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"); } }