Files
itsc-timetable/components/Timetable.tsx
2023-02-03 22:47:51 +08:00

202 lines
5.5 KiB
TypeScript

import React from "react";
interface Conflicts {
[index: string]: HTMLInputElement[];
}
interface ConflictsTmp {
[index: string]: Set<string>;
}
interface IndexToElement {
[index: string]: HTMLInputElement;
}
const indexToElement: IndexToElement = {};
const conflicts: Conflicts = {};
const marks: (HTMLInputElement | null)[][] = [];
const Timetable = ({ user }) => {
const [editable, setEditable] = React.useState(true);
const ref = React.useRef();
const handleSelect = async (event: Event) => {
const target: HTMLInputElement = event.target;
console.log("select", target.name, target.checked);
const changedInputs: any = [];
// find whether there are checked input in conflict
for (const input of conflicts[target.name]) {
if (input.name === target.name) continue;
if (input.checked) {
alert("Error: Conflict select");
location.reload();
return;
}
}
for (const input of conflicts[target.name]) {
if (input.name === target.name) continue;
if (target.checked) {
if (input.getAttribute("disabled") === null) {
input.setAttribute("disabled", "true");
changedInputs.push({ input, disable: false });
}
} else {
if (input.getAttribute("disabled") === "true") {
input.removeAttribute("disabled");
changedInputs.push({ input, disable: true });
}
}
}
// post request
const resp = await fetch("/api/record", {
method: "POST",
headers: { "Content-Type": "appliction/json" },
body: JSON.stringify({
name: target.name,
checked: target.checked,
user,
}),
});
if (!resp.ok) {
const json = await resp.json();
alert(json.error);
// revert conflict changed input
for (const { input, disable } of changedInputs) {
if (disable) {
input.setAttribute("disabled", "true");
} else {
input.removeAttribute("disabled");
}
}
}
};
const handleInput = (event: React.ChangeEvent<HTMLInputElement>): boolean => {
const { target } = event;
// validate
if (target?.children[0]?.tagName !== "TABLE") {
console.log("not a table");
return false;
}
console.log(target.innerHTML);
// turn off editable
setEditable(false);
const table = target.children[0];
table.setAttribute("border", "1");
// mark cell
const conflictsTmp: ConflictsTmp = {};
const tbody = table.children[table.children.length - 1];
for (const tr_index in tbody.children) {
const tr = tbody.children[tr_index];
const row: (HTMLInputElement | null)[] = [];
for (const td_index in tr.children) {
const td = tr.children[td_index];
if (td.tagName !== "TD") continue;
if (td.getAttribute("bgcolor")?.toUpperCase() !== "#39CEFF") {
row.push(null);
continue;
}
const index = `${tr_index},${td_index}`;
const placeholders = td.textContent?.trim().split(",");
if (placeholders === undefined) continue;
if (conflictsTmp[index] === undefined) conflictsTmp[index] = new Set();
for (const ph of placeholders) conflictsTmp[index].add(ph);
// mount click event
const input = document.createElement("input");
input.setAttribute("type", "checkbox");
input.onchange = handleSelect;
input.name = index;
td.innerHTML = "";
td.appendChild(input);
indexToElement[index] = input;
row.push(input);
}
marks.push(row);
}
// resolve conflicts
for (const index in conflictsTmp) {
if (conflicts[index] === undefined) conflicts[index] = [];
for (const ph of Array.from(conflictsTmp[index])) {
for (const conflictIndex in conflictsTmp) {
if (conflictsTmp[conflictIndex].has(ph))
conflicts[index].push(indexToElement[conflictIndex]);
}
}
}
console.log(conflicts);
return true;
};
const refresh = async () => {
const resp = await fetch(`/api/record?name=${user}`);
const json = await resp.json();
const occupied: string[] = json.occupied;
const myselect: string[] = json.myselect;
console.log(json);
for (const index in indexToElement) {
if (occupied.includes(index)) {
indexToElement[index].style.display = "none";
} else {
indexToElement[index].style.display = "";
}
const includes = myselect.includes(index);
indexToElement[index].checked = includes;
// after checked, find conflicts input
if (includes) {
for (const input of conflicts[index]) {
if (input.name === index) continue;
input.setAttribute("disabled", "true");
}
}
}
};
React.useEffect(() => {
const interval = setInterval(() => {
refresh();
}, 1000);
return () => {
clearInterval(interval);
};
});
React.useEffect(() => {
fetch("/api/html")
.then((resp) => resp.json())
.then((json) => {
console.log(ref);
ref.current.innerHTML = json.html;
})
.then(() => {
handleInput({ target: ref.current });
refresh();
});
}, []);
return (
<>
<button onClick={refresh}>Test</button>
<span>Login as {user}</span>
<div
ref={ref}
contentEditable={editable}
style={{
overflow: "scroll",
}}
onInput={handleInput}
></div>
</>
);
};
export default Timetable;