diff --git a/.gitignore b/.gitignore
index c87c9b3..ac28837 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+/html.html
+/json
+/hours.json
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
diff --git a/components/Timetable.tsx b/components/Timetable.tsx
index 975b5e1..3e6b3fa 100644
--- a/components/Timetable.tsx
+++ b/components/Timetable.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { get, post } from "@/common";
+import { collection } from "@/store";
interface Conflicts {
[index: string]: HTMLInputElement[];
@@ -15,7 +16,24 @@ const indexToElement: IndexToElement = {};
const conflicts: Conflicts = {};
const marks: (HTMLInputElement | null)[][] = [];
-const Timetable = ({ user }) => {
+const downloadObjectAsJson = (exportObj: any, exportName: string) => {
+ const dataStr =
+ "data:text/json;charset=utf-8," +
+ encodeURIComponent(JSON.stringify(exportObj));
+ const downloadAnchorNode = document.createElement("a");
+ downloadAnchorNode.setAttribute("href", dataStr);
+ downloadAnchorNode.setAttribute("download", `${exportName}.json`);
+ document.body.appendChild(downloadAnchorNode); // required for firefox
+ downloadAnchorNode.click();
+ downloadAnchorNode.remove();
+};
+
+const Timetable = ({
+ user,
+ disableNetwork = false,
+ disableConflictCheck = false,
+ replaceInputType = "checkbox",
+}) => {
const [editable, setEditable] = React.useState(true);
const ref = React.useRef();
@@ -23,6 +41,8 @@ const Timetable = ({ user }) => {
const target: HTMLInputElement = event.target;
console.log("select", target.name, target.checked);
+ if (disableConflictCheck) return;
+
const changedInputs: any = [];
// find whether there are checked input in conflict
for (const input of conflicts[target.name]) {
@@ -47,6 +67,9 @@ const Timetable = ({ user }) => {
}
}
}
+
+ if (disableNetwork) return;
+
// post request
const json = await post("/api/record", {
name: target.name,
@@ -74,11 +97,12 @@ const Timetable = ({ user }) => {
return false;
}
- console.log(target.innerHTML);
-
// turn off editable
setEditable(false);
+ // empty marks
+ marks.length = 0;
+
const table = target.children[0];
table.setAttribute("border", "1");
@@ -105,7 +129,7 @@ const Timetable = ({ user }) => {
// mount click event
const input = document.createElement("input");
- input.setAttribute("type", "checkbox");
+ input.setAttribute("type", replaceInputType);
input.onchange = handleSelect;
input.name = index;
td.innerHTML = "";
@@ -115,6 +139,7 @@ const Timetable = ({ user }) => {
row.push(input);
}
marks.push(row);
+ // console.log("marks", marks);
}
// resolve conflicts
@@ -156,13 +181,15 @@ const Timetable = ({ user }) => {
}
};
React.useEffect(() => {
+ if (disableNetwork) return;
+
const interval = setInterval(() => {
refresh();
}, 1000);
return () => {
clearInterval(interval);
};
- });
+ }, []);
React.useEffect(() => {
const main = async () => {
@@ -176,7 +203,27 @@ const Timetable = ({ user }) => {
return (
<>
-
+
Login as {user}
{
}}
onInput={handleInput}
>
+
>
);
};
diff --git a/package-lock.json b/package-lock.json
index 68d2879..e76251a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,12 +9,15 @@
"version": "0.1.0",
"dependencies": {
"@next/font": "13.1.6",
+ "@types/glob": "^8.0.1",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"esbuild": "^0.17.5",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
+ "glob": "^8.1.0",
+ "mongodb": "5.0",
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -420,6 +423,22 @@
"glob": "7.1.7"
}
},
+ "node_modules/@next/eslint-plugin-next/node_modules/glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/@next/font": {
"version": "13.1.6",
"resolved": "https://registry.npmmirror.com/@next/font/-/font-13.1.6.tgz",
@@ -681,11 +700,25 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/glob": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-8.0.1.tgz",
+ "integrity": "sha512-8bVUjXZvJacUFkJXHdyZ9iH1Eaj5V7I8c4NdH5sQJsdXkqT4CA5Dhb4yb4VE/3asyx4L9ayZr1NIhTsWHczmMw==",
+ "dependencies": {
+ "@types/minimatch": "^5.1.2",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA=="
+ },
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.11.18.tgz",
@@ -719,6 +752,20 @@
"resolved": "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog=="
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmmirror.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
+ "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/webidl-conversions": "*"
+ }
+ },
"node_modules/@typescript-eslint/parser": {
"version": "5.50.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.50.0.tgz",
@@ -974,6 +1021,14 @@
"node": ">=8"
}
},
+ "node_modules/bson": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/bson/-/bson-5.0.0.tgz",
+ "integrity": "sha512-EL2KpZdyhshyyptj6pnQfnFKPoncD9KwZYvgmj/FXQiOUU1HWTHWmBOP4TZXU3YzStcI5qgpIl68YnMo16s26A==",
+ "engines": {
+ "node": ">=14.20.1"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
@@ -1882,19 +1937,18 @@
"integrity": "sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ=="
},
"node_modules/glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
},
"engines": {
- "node": "*"
+ "node": ">=12"
}
},
"node_modules/glob-parent": {
@@ -1908,6 +1962,25 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/glob/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/globals": {
"version": "13.20.0",
"resolved": "https://registry.npmmirror.com/globals/-/globals-13.20.0.tgz",
@@ -2088,6 +2161,11 @@
"node": ">= 0.4"
}
},
+ "node_modules/ip": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/ip/-/ip-2.0.0.tgz",
+ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
+ },
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -2449,6 +2527,12 @@
"node": ">=10"
}
},
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "optional": true
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
@@ -2485,6 +2569,47 @@
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
+ "node_modules/mongodb": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/mongodb/-/mongodb-5.0.1.tgz",
+ "integrity": "sha512-KpjtY+NWFmcic6UDYEdfn768ZTuKyv7CRaui7ZSd6q/0/o1AURMC7KygTUwB1Cl8V10Pe5NiP+Y2eBMCDs/ygQ==",
+ "dependencies": {
+ "bson": "^5.0.0",
+ "mongodb-connection-string-url": "^2.6.0",
+ "socks": "^2.7.1"
+ },
+ "engines": {
+ "node": ">=14.20.1"
+ },
+ "optionalDependencies": {
+ "saslprep": "^1.0.3"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.201.0",
+ "mongodb-client-encryption": "^2.3.0",
+ "snappy": "^7.2.2"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmmirror.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
+ "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
+ "dependencies": {
+ "@types/whatwg-url": "^8.2.1",
+ "whatwg-url": "^11.0.0"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
@@ -2911,6 +3036,22 @@
"rimraf": "bin.js"
}
},
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -2929,6 +3070,18 @@
"is-regex": "^1.1.4"
}
},
+ "node_modules/saslprep": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/saslprep/-/saslprep-1.0.3.tgz",
+ "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+ "optional": true,
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.0.tgz",
@@ -2988,6 +3141,28 @@
"node": ">=8"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmmirror.com/socks/-/socks-2.7.1.tgz",
+ "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+ "dependencies": {
+ "ip": "^2.0.0",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0",
+ "npm": ">= 3.0.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -2996,6 +3171,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "optional": true,
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -3155,6 +3339,17 @@
"node": ">=8.0"
}
},
+ "node_modules/tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "dependencies": {
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -3250,6 +3445,26 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "dependencies": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
@@ -3518,6 +3733,21 @@
"integrity": "sha512-o7cauUYsXjzSJkay8wKjpKJf2uLzlggCsGUkPu3lP09Pv97jYlekTC20KJrjQKmSv5DXV0R/uks2ZXhqjNkqAw==",
"requires": {
"glob": "7.1.7"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
"@next/font": {
@@ -3652,11 +3882,25 @@
"tslib": "^2.4.0"
}
},
+ "@types/glob": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmmirror.com/@types/glob/-/glob-8.0.1.tgz",
+ "integrity": "sha512-8bVUjXZvJacUFkJXHdyZ9iH1Eaj5V7I8c4NdH5sQJsdXkqT4CA5Dhb4yb4VE/3asyx4L9ayZr1NIhTsWHczmMw==",
+ "requires": {
+ "@types/minimatch": "^5.1.2",
+ "@types/node": "*"
+ }
+ },
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
+ "@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA=="
+ },
"@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.11.18.tgz",
@@ -3690,6 +3934,20 @@
"resolved": "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
+ "@types/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog=="
+ },
+ "@types/whatwg-url": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmmirror.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
+ "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
+ "requires": {
+ "@types/node": "*",
+ "@types/webidl-conversions": "*"
+ }
+ },
"@typescript-eslint/parser": {
"version": "5.50.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.50.0.tgz",
@@ -3882,6 +4140,11 @@
"fill-range": "^7.0.1"
}
},
+ "bson": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/bson/-/bson-5.0.0.tgz",
+ "integrity": "sha512-EL2KpZdyhshyyptj6pnQfnFKPoncD9KwZYvgmj/FXQiOUU1HWTHWmBOP4TZXU3YzStcI5qgpIl68YnMo16s26A=="
+ },
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz",
@@ -4621,16 +4884,33 @@
"integrity": "sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ=="
},
"glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmmirror.com/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
}
},
"glob-parent": {
@@ -4785,6 +5065,11 @@
"side-channel": "^1.0.4"
}
},
+ "ip": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/ip/-/ip-2.0.0.tgz",
+ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
+ },
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -5074,6 +5359,12 @@
"yallist": "^4.0.0"
}
},
+ "memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmmirror.com/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "optional": true
+ },
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
@@ -5101,6 +5392,26 @@
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
+ "mongodb": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/mongodb/-/mongodb-5.0.1.tgz",
+ "integrity": "sha512-KpjtY+NWFmcic6UDYEdfn768ZTuKyv7CRaui7ZSd6q/0/o1AURMC7KygTUwB1Cl8V10Pe5NiP+Y2eBMCDs/ygQ==",
+ "requires": {
+ "bson": "^5.0.0",
+ "mongodb-connection-string-url": "^2.6.0",
+ "saslprep": "^1.0.3",
+ "socks": "^2.7.1"
+ }
+ },
+ "mongodb-connection-string-url": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmmirror.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
+ "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
+ "requires": {
+ "@types/whatwg-url": "^8.2.1",
+ "whatwg-url": "^11.0.0"
+ }
+ },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
@@ -5408,6 +5719,21 @@
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
}
},
"run-parallel": {
@@ -5428,6 +5754,15 @@
"is-regex": "^1.1.4"
}
},
+ "saslprep": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/saslprep/-/saslprep-1.0.3.tgz",
+ "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+ "optional": true,
+ "requires": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
"scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.0.tgz",
@@ -5472,11 +5807,34 @@
"resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
+ "smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
+ },
+ "socks": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmmirror.com/socks/-/socks-2.7.1.tgz",
+ "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
+ "requires": {
+ "ip": "^2.0.0",
+ "smart-buffer": "^4.2.0"
+ }
+ },
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
+ "sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "optional": true,
+ "requires": {
+ "memory-pager": "^1.0.2"
+ }
+ },
"stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@@ -5595,6 +5953,14 @@
"is-number": "^7.0.0"
}
},
+ "tr46": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/tr46/-/tr46-3.0.0.tgz",
+ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+ "requires": {
+ "punycode": "^2.1.1"
+ }
+ },
"tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -5673,6 +6039,20 @@
"punycode": "^2.1.0"
}
},
+ "webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
+ },
+ "whatwg-url": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-11.0.0.tgz",
+ "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+ "requires": {
+ "tr46": "^3.0.0",
+ "webidl-conversions": "^7.0.0"
+ }
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 55d8ad4..d216179 100644
--- a/package.json
+++ b/package.json
@@ -11,12 +11,15 @@
},
"dependencies": {
"@next/font": "13.1.6",
+ "@types/glob": "^8.0.1",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"esbuild": "^0.17.5",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
+ "glob": "^8.1.0",
+ "mongodb": "5.0",
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
diff --git a/pages/api/tool.ts b/pages/api/tool.ts
new file mode 100644
index 0000000..9c85632
--- /dev/null
+++ b/pages/api/tool.ts
@@ -0,0 +1,101 @@
+import type { NextApiRequest, NextApiResponse } from "next";
+import fs from "fs";
+import { store, html } from "@/store";
+import glob from "glob";
+import { promisify } from "util";
+const g = promisify(glob);
+const read = promisify(fs.readFile);
+
+// 索引与工时
+const indexToHour: Record = JSON.parse(
+ fs.readFileSync("./hours.json", "utf8")
+).selections;
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse>
+) {
+ // 读入全部json文件
+ // users: {姓名: {坐标: 权重}}
+ const users: Record> = {};
+ const files = await g("./json/*.json");
+ for (const file of files) {
+ const jsonStr = await read(file, "utf8");
+ const json: {
+ user: string;
+ selections: Record;
+ } = JSON.parse(jsonStr);
+
+ // Normalization
+ // 使用 标准分数 算法 z = (x - mean) / std
+ const { selections } = json;
+ const nums = Object.values(selections);
+ const mean = nums.reduce((a, b) => a + b) / nums.length;
+ const std = Math.sqrt(
+ nums.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
+ nums.length
+ );
+ const normalizedNums = nums.map((x) => (std === 0 ? 0 : (x - mean) / std));
+ for (const index in selections) {
+ selections[index] = std === 0 ? 0 : (selections[index] - mean) / std;
+ }
+ users[json.user] = selections;
+ }
+ // 计算每一个格子有多少人选择
+ // counts: {坐标: {姓名: 权重}}
+ const counts: Record>> = {};
+ for (const user in users) {
+ for (const select in users[user]) {
+ if (counts[select] === undefined) counts[select] = {};
+ counts[select][user] = users[user];
+ }
+ }
+
+ // 构造返回数据, key 为 坐标,value 为姓名
+ const resp: Record = {};
+
+ // 找到选择数量最少的格子
+ // sortable: [[坐标, 选择人数]]
+ // 如果遇到选择数量相同的格子,随机打乱顺序进行迭代
+ const sortable: [string, number][] = [];
+ for (const index in counts) {
+ sortable.push([index, Object.keys(counts[index]).length]);
+ }
+ sortable.sort((a, b) => a[1] - b[1] || Math.random() - 0.5);
+
+ // 记录这个人的出现次数,还有这个人的工时总数
+ const hoursCount: Record = {};
+
+ for (const [smallestIndex, _] of sortable) {
+ // console.log("smallestIndex", smallestIndex);
+ // console.log("cell", counts[smallestIndex]);
+ // 找到格子里权重最低的人选 [姓名, 小时数, 出现次数, 权重]
+ const weightList: [string, number, number][] = [];
+ for (const user in counts[smallestIndex]) {
+ weightList.push([
+ user,
+ hoursCount[user] || 0,
+ counts[smallestIndex][user][smallestIndex],
+ ]);
+ }
+ weightList.sort((a, b) => a[1] - b[1] || a[2] - b[2]);
+ // console.log("weightList", weightList);
+ const theChoosenUser = weightList[0][0];
+ // console.log("theChoosenUser", theChoosenUser);
+ // 记录结果
+ resp[smallestIndex] = theChoosenUser;
+ hoursCount[theChoosenUser] =
+ (hoursCount[theChoosenUser] || 0) + indexToHour[smallestIndex];
+ }
+ // console.log("hoursCount", hoursCount);
+
+ const sortedHoursCount: [string, number][] = [];
+ for (const user in hoursCount) {
+ sortedHoursCount.push([user, hoursCount[user]]);
+ }
+ sortedHoursCount.sort((a, b) => a[1] - b[1]);
+ console.log("sortedHoursCount", sortedHoursCount);
+ console.log("sortedHoursCount.length", sortedHoursCount.length);
+
+ res.status(200).json(resp);
+}
diff --git a/pages/report-tool.tsx b/pages/report-tool.tsx
new file mode 100644
index 0000000..f8a845b
--- /dev/null
+++ b/pages/report-tool.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import Head from "next/head";
+
+const ReportPage = () => {
+ const ref = React.useRef();
+ const getReport = async () => {
+ const resp = await fetch("/api/html").then((resp) => resp.json());
+ ref.current.innerHTML = resp.html;
+ const json: Record = await fetch("/api/tool").then(
+ (resp) => resp.json()
+ );
+ const table = ref.current.children[0];
+ const tbody = table.children[table.children.length - 1];
+ for (const tr_index in tbody.children) {
+ const tr = tbody.children[tr_index];
+ for (const td_index in tr.children) {
+ const td = tr.children[td_index];
+ if (td.tagName !== "TD") continue;
+ const index = `${tr_index},${td_index}`;
+ if (json[index] === undefined) continue;
+ td.innerHTML = json[index];
+ }
+ }
+ };
+ React.useEffect(() => {
+ getReport();
+ }, []);
+ return (
+ <>
+
+ Create Next App
+
+
+
+
+
+
+ {
+ console.log(event.currentTarget.innerHTML);
+ }}
+ contentEditable="true"
+ >
+
+ >
+ );
+};
+
+export default ReportPage;
\ No newline at end of file
diff --git a/pages/tool.tsx b/pages/tool.tsx
new file mode 100644
index 0000000..66b074a
--- /dev/null
+++ b/pages/tool.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import Head from "next/head";
+import Timetable from "@/components/Timetable";
+
+export default function Home() {
+ const [user, setUser] = React.useState("");
+ const [begin, setBegin] = React.useState(false);
+
+ React.useEffect(() => {
+ setUser(localStorage.getItem("user") || "");
+ }, []);
+
+ return (
+ <>
+
+ Create Next App
+
+
+
+
+
+ {!begin && (
+
+ setUser(event.target.value)}
+ />
+
+
+ )}
+ {begin && (
+
+ )}
+
+ >
+ );
+}
diff --git a/store/index.ts b/store/index.ts
index 53f4c1f..7c21592 100644
--- a/store/index.ts
+++ b/store/index.ts
@@ -1,3 +1,10 @@
+import fs from "fs";
+import { MongoClient } from "mongodb";
+import util from "util";
+
+const write = util.promisify(fs.writeFile);
+const read = util.promisify(fs.readFile);
+
class Store {
record: Record;
constructor() {
@@ -27,13 +34,20 @@ class Store {
class HTML {
html: string;
constructor() {
- this.html = "";
+ // load from file
+ try {
+ this.html = fs.readFileSync("./html.html", "utf8");
+ } catch {
+ this.html = "";
+ }
}
public get() {
return this.html;
}
- public set(html) {
+ public async set(html: string) {
this.html = html;
+ // store into file
+ await write("./html.html", html, "utf8");
}
}