init
This commit is contained in:
193
components/Timetable.tsx
Normal file
193
components/Timetable.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
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.addAttribute("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 = "";
|
||||
}
|
||||
indexToElement[index].checked = myselect.includes(index);
|
||||
}
|
||||
};
|
||||
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;
|
||||
Reference in New Issue
Block a user