Compare commits
7 Commits
89124b9ad9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3db0a652ce | ||
|
|
1049c8c8df | ||
|
|
fd3dd85963 | ||
| e8ce8492cf | |||
|
|
ffc89fd069 | ||
|
|
bebf2c9640 | ||
|
a04f1c22c9
|
@@ -1,14 +1,17 @@
|
||||
const API_PREFIX = "";
|
||||
|
||||
export const get = async (url: string) => {
|
||||
const resp = await fetch(url);
|
||||
const resp = await fetch(`${API_PREFIX}${url}`);
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
export const post = async (
|
||||
url: string,
|
||||
json: any,
|
||||
headers: Record<string, string> = {}
|
||||
) => {
|
||||
const resp = await fetch(url, {
|
||||
const resp = await fetch(`${API_PREFIX}${url}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
body: JSON.stringify(json),
|
||||
|
||||
@@ -79,10 +79,13 @@ const Timetable = ({
|
||||
|
||||
if (disableNetwork) return;
|
||||
|
||||
// Immediately revert the checkbox state
|
||||
target.checked = !target.checked;
|
||||
|
||||
// post request
|
||||
const json = await post("/api/record", {
|
||||
name: target.name,
|
||||
checked: target.checked,
|
||||
checked: !target.checked,
|
||||
user,
|
||||
});
|
||||
if (json.error !== undefined) {
|
||||
@@ -100,6 +103,7 @@ const Timetable = ({
|
||||
|
||||
const handleInput = (event: React.ChangeEvent<HTMLInputElement>): boolean => {
|
||||
const { target } = event;
|
||||
|
||||
// validate
|
||||
if (target?.children[0]?.tagName !== "TABLE") {
|
||||
console.log("not a table");
|
||||
@@ -115,7 +119,7 @@ const Timetable = ({
|
||||
tds.length = 0;
|
||||
|
||||
const table = target.children[0];
|
||||
table.setAttribute("border", "1");
|
||||
table.setAttribute("border", "0");
|
||||
|
||||
// mark cell
|
||||
const conflictsTmp: ConflictsTmp = {};
|
||||
@@ -202,7 +206,7 @@ const Timetable = ({
|
||||
}
|
||||
const occupied: string[] = json.occupied;
|
||||
const myselect: string[] = json.myselect;
|
||||
console.log(json);
|
||||
// console.log(json);
|
||||
for (const index in indexToElement) {
|
||||
if (occupied.includes(index)) {
|
||||
indexToElement[index].style.display = "none";
|
||||
@@ -226,7 +230,7 @@ const Timetable = ({
|
||||
|
||||
const interval = setInterval(() => {
|
||||
refresh();
|
||||
}, 1000);
|
||||
}, 1500);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
@@ -240,6 +244,15 @@ const Timetable = ({
|
||||
refresh();
|
||||
};
|
||||
main();
|
||||
|
||||
// Auto refresh the entire timetable every 3 minutes
|
||||
const fullRefreshInterval = setInterval(() => {
|
||||
main();
|
||||
}, 3 * 60 * 1000); // 3 minutes in milliseconds
|
||||
|
||||
return () => {
|
||||
clearInterval(fullRefreshInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const DownloadMarks = async () => {
|
||||
@@ -264,17 +277,18 @@ const Timetable = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 style={{ textAlign: "center" }}>Login as {user}</h2>
|
||||
<h2 style={{ textAlign: "center" }}>ITSC 学 生 助 理 抢 班 系 统</h2>
|
||||
<h3 style={{ textAlign: "center" }}>Login as {user}</h3>
|
||||
<h3 style={{ textAlign: "center" }}>请勿在短时间内多次操作同一时段的选择,选择后请耐心等待状态更新以确认是否选择成功</h3>
|
||||
<div
|
||||
align="center"
|
||||
ref={ref}
|
||||
contentEditable={editable}
|
||||
style={{
|
||||
overflow: "scroll",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
onInput={handleInput}
|
||||
></div>{" "}
|
||||
></div>
|
||||
{!hideDownloadButton && (
|
||||
<p style={{ display: "flex", justifyContent: "center" }}>
|
||||
<button onClick={DownloadMarks}>Download Selection</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ const UserInputWrap = ({ children, setUser }) => {
|
||||
const [begin, setBegin] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setInputUser(localStorage.getItem("user") || "");
|
||||
setInputUser(localStorage.getItem("username") || "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -17,9 +17,10 @@ const UserInputWrap = ({ children, setUser }) => {
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "100vh",
|
||||
// minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
<h2 style={{ textAlign: "center" }}>ITSC 学 生 助 理 抢 班 系 统</h2>
|
||||
<div>
|
||||
<input
|
||||
style={{
|
||||
@@ -45,7 +46,7 @@ const UserInputWrap = ({ children, setUser }) => {
|
||||
}
|
||||
setUser(inputUser.trim());
|
||||
setBegin(true);
|
||||
localStorage.setItem("user", inputUser.trim());
|
||||
localStorage.setItem("username", inputUser.trim());
|
||||
}}
|
||||
>
|
||||
Login
|
||||
|
||||
BIN
data-init.zip
Normal file
BIN
data-init.zip
Normal file
Binary file not shown.
@@ -4,9 +4,9 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"esbuild": "./node_modules/.bin/esbuild --bundle --tsconfig=entrypoints/tsconfig.json --alias:'@'='./' --outdir=entrypoints --splitting --format=esm entrypoints/index.tsx entrypoints/edit/index.tsx entrypoints/report/index.tsx entrypoints/control/index.tsx --minify",
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 4000",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start": "next start -p 4000",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -4,13 +4,14 @@ import config from "@/config";
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
if (req.headers.token !== config.token) {
|
||||
console.log("wrong token", req.headers.token, config.token);
|
||||
console.log("api::config: wrong token", req.headers.token, config.token);
|
||||
res.status(403).json({ error: "wrong token" });
|
||||
return;
|
||||
}
|
||||
// update config
|
||||
config.begin = req.body.begin ?? config.begin;
|
||||
config.limit = req.body.limit ?? config.limit;
|
||||
console.log("api::config: update config", config);
|
||||
}
|
||||
res.status(200).json(config);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import config from "@/config";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const store = storeProxy.get();
|
||||
// console.log("api::store.null()", store);
|
||||
if (req.method === "POST") {
|
||||
if (!config.begin) {
|
||||
res.status(400).json({
|
||||
@@ -13,7 +14,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
const json = req.body;
|
||||
console.log("request", json);
|
||||
console.log("api::request: new request", json);
|
||||
if (json.checked) {
|
||||
let count = 0;
|
||||
for (const name in store) {
|
||||
@@ -21,33 +22,42 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
count += 1;
|
||||
if (count >= config.limit) {
|
||||
res.status(403).json({
|
||||
error: `超过选择数量限制: ${config.limit}`,
|
||||
error: `超过选择数量限制,您至多选 ${config.limit} 个班次`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// check whether it is alreadly occupied
|
||||
if (store[json.name] !== undefined) {
|
||||
// check whether the user repeatly select
|
||||
if (store[json.name] === json.user) {
|
||||
console.log("api::request: repeat select", json);
|
||||
res.status(403).json({
|
||||
error: `当前位置已被他人占用`,
|
||||
error: `您已经选择了这个班次,请勿重复选择`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// check whether it is already occupied
|
||||
else if (store[json.name] !== undefined) {
|
||||
console.log("api::request: occupied", json);
|
||||
res.status(403).json({
|
||||
error: `当前位置已被他人占用,请选择其他班次`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
store[json.name] = json.user;
|
||||
} else {
|
||||
// console.log(store, json);
|
||||
// check whether the request name match the taken name
|
||||
if (store[json.name] !== json.user) {
|
||||
res.status(403).json({
|
||||
error: `失败:您未选择到当前位置`,
|
||||
error: `您已经取消了这个班次,请勿重复点击复选框`,
|
||||
})
|
||||
return;
|
||||
}
|
||||
delete store[json.name];
|
||||
}
|
||||
}
|
||||
console.log("query", req.query);
|
||||
const resp = {
|
||||
const resp: { occupied: string[], myselect: string[] } = { // try to fix
|
||||
occupied: [],
|
||||
myselect: [],
|
||||
};
|
||||
|
||||
@@ -9,9 +9,9 @@ export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>抢 班</title>
|
||||
<meta name="description" content="ITSC 学 生 助 理 抢 班 系 统" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<main>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { get, post } from "@/common";
|
||||
|
||||
const ReportPage = () => {
|
||||
const ref = React.useRef();
|
||||
const getReport = async () => {
|
||||
const resp = await fetch("/api/html").then((resp) => resp.json());
|
||||
const resp = await get("/api/html");
|
||||
ref.current.innerHTML = resp.html;
|
||||
const json: Record<string, string> = await fetch("/api/tool").then((resp) =>
|
||||
resp.json()
|
||||
);
|
||||
const json: Record<string, string> = await get("/api/tool");
|
||||
const table = ref.current.children[0];
|
||||
const tbody = table.children[table.children.length - 1];
|
||||
for (const tr_index in tbody.children) {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from "react";
|
||||
import Head from "next/head";
|
||||
import { get, post } from "@/common";
|
||||
|
||||
const ReportPage = () => {
|
||||
const ref = React.useRef();
|
||||
const getReport = async () => {
|
||||
const resp = await fetch("/api/html").then((resp) => resp.json());
|
||||
const resp = await get("/api/html");
|
||||
ref.current.innerHTML = resp.html;
|
||||
const json: Record<string, string> = await fetch("/api/admin").then(
|
||||
(resp) => resp.json()
|
||||
);
|
||||
const json: Record<string, string> = await get("/api/admin");
|
||||
const table = ref.current.children[0];
|
||||
const tbody = table.children[table.children.length - 1];
|
||||
for (const tr_index in tbody.children) {
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function Home() {
|
||||
const [begin, setBegin] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setUser(localStorage.getItem("user") || "");
|
||||
setUser(localStorage.getItem("username") || "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -34,7 +34,7 @@ export default function Home() {
|
||||
}
|
||||
setUser(user.trim());
|
||||
setBegin(true);
|
||||
localStorage.setItem("user", user);
|
||||
localStorage.setItem("username", user);
|
||||
}}
|
||||
>
|
||||
Login
|
||||
|
||||
@@ -30,7 +30,13 @@ class Store {
|
||||
await this.save();
|
||||
}
|
||||
public async save() {
|
||||
await write(this.filename, JSON.stringify(this.record), "utf8");
|
||||
// try first, then catch
|
||||
try {
|
||||
console.log("store::index: save record", this.record);
|
||||
await write(this.filename, JSON.stringify(this.record), "utf8");
|
||||
} catch {
|
||||
console.error("store::index: save record error, filename:", this.filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +58,16 @@ class HTML {
|
||||
public async set(html: string) {
|
||||
this.html = html;
|
||||
// store into file
|
||||
await write(this.filename, html, "utf8");
|
||||
// try first, then catch
|
||||
try {
|
||||
await write(this.filename, html, "utf8");
|
||||
} catch {
|
||||
console.error("store::index: save record error, filename:", this.filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const html = new HTML("./data/html.html");
|
||||
export const html = new HTML("./data/html-current.html");
|
||||
export const htmlRegular = new HTML("./data/html-regular.html");
|
||||
export const store = new Store("./data/store.json");
|
||||
export const regular = new Store("./data/regular.json");
|
||||
|
||||
Reference in New Issue
Block a user