temporarily overriding attendance, roster fix

This commit is contained in:
Aaron Lee 2024-01-17 01:08:40 +08:00
parent e02db95f55
commit 676b50db0c
4 changed files with 200 additions and 47 deletions

View file

@ -8,9 +8,10 @@ import { api } from "~/utils/api";
export default function Dash() { export default function Dash() {
const { push } = useRouter(); const { push } = useRouter();
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
const [genDate, setGenDate] = useState(new Date(0)); const [genDate, setGenDate] = useState(new Date());
const { toPDF, targetRef: pdfRef } = usePDF({ filename: `入校名單_${date.toLocaleDateString().replace("/", "-")}.pdf` }); const { toPDF, targetRef: pdfRef } = usePDF({ filename: `入校名單_${date.toLocaleDateString().replace("/", "-")}.pdf` });
const { toPDF: bigToPdf, targetRef: bigPdfRef } = usePDF({ filename: `Build_Season_總表.pdf` });
const isLoggedIn = api.admin.isLoggedIn.useQuery(); const isLoggedIn = api.admin.isLoggedIn.useQuery();
const periods = api.admin.getPeriods.useQuery(); const periods = api.admin.getPeriods.useQuery();
@ -18,7 +19,9 @@ export default function Dash() {
const roster = api.admin.getRoster.useQuery({ const roster = api.admin.getRoster.useQuery({
date: date date: date
}); });
const allRoster = api.admin.getAllRoster.useQuery();
const allPeriodsHeadcount = api.admin.getAllPeriodsHeadcount.useQuery();
useEffect(() => { useEffect(() => {
if (isLoggedIn.failureCount > 0) { if (isLoggedIn.failureCount > 0) {
push("/"); push("/");
@ -60,6 +63,16 @@ export default function Dash() {
}}> }}>
Download PDF Download PDF
</button> </button>
<button className="h-fit bg-emerald-600 px-3 py-2 rounded text-white focus:ring focus:ring-emerald-200 focus:ring-opacity-70 disabled:bg-emerald-400 mb-5" disabled={roster.isLoading}
onClick={() => {
bigToPdf({
page: {
orientation: "landscape"
}
});
}}>
Download PDF
</button>
{ {
(roster.isLoading) && <div role="status"> (roster.isLoading) && <div role="status">
<svg aria-hidden="true" className="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-emerald-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg aria-hidden="true" className="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-emerald-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -80,6 +93,7 @@ export default function Dash() {
<table className="table-auto border-collapse border-2 border-black w-fit mt-3 font-kai text-lg"> <table className="table-auto border-collapse border-2 border-black w-fit mt-3 font-kai text-lg">
<thead> <thead>
<tr className="*:p-1 *:border border-b-2 border-b-black"> <tr className="*:p-1 *:border border-b-2 border-b-black">
<th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
@ -95,6 +109,7 @@ export default function Dash() {
{ {
roster.data?.map((user) => { roster.data?.map((user) => {
return (<tr key={user.id} className="*:p-1 *:border"> return (<tr key={user.id} className="*:p-1 *:border">
<td>{user.username}</td>
<td>{user.grade}</td> <td>{user.grade}</td>
<td>{user.class}</td> <td>{user.class}</td>
<td>{user.number}</td> <td>{user.number}</td>
@ -114,6 +129,73 @@ export default function Dash() {
</tbody> </tbody>
</table> </table>
</div> </div>
<hr />
<div className="pdf my-5 w-fit p-5" ref={bigPdfRef}>
<h1 className="text-3xl font-extrabold font-kai mb-2"> Build Season </h1>
<div className="flex mb-2 justify-between font-extrabold font-kai items-end">
<h3 className="m-0">: {genDate.toLocaleString()}</h3>
</div>
<table className="table-auto border-collapse border-2 border-black w-fit mt-3 font-kai text-lg">
<thead>
<tr className="*:p-1 *:border">
<th rowSpan={2}></th>
<th rowSpan={2}></th>
<th rowSpan={2}></th>
<th rowSpan={2}></th>
{
Object.keys(periods.data ?? {}).map((date) => {
return (<th key={date} colSpan={
periods.data![date]!.length
} className="w-24">{date}</th>)
})
}
</tr>
<tr className="*:p-1 *:border border-b-2 border-b-black">
{
Object.keys(periods.data ?? {}).map((date) => {
return periods.data![date]!.map((period) => {
return (<th key={period.id}>{period.timePeriod.name}</th>)
})
})
}
</tr>
</thead>
<tbody>
{
allRoster.data?.map((user) => {
return (<tr key={user.id} className="*:p-1 *:border">
<td>{user.grade}</td>
<td>{user.class}</td>
<td>{user.number}</td>
<td>{user.name}</td>
{
Object.keys(periods.data ?? {}).map((date) => {
return periods.data![date]!.map((thisPeriod) => {
if (user.periods.find((period) => period.id === thisPeriod.id)) {
return (<td key={thisPeriod.id * user.id}></td>)
} else {
return (<td key={thisPeriod.id * user.id}></td>)
}
})
})
}
</tr>)
})
}
</tbody>
<tfoot>
<tr className="*:p-1 *:border">
<td colSpan={4}></td>
{
allPeriodsHeadcount.data?.map((headcount) => {
return (<td key={headcount.id}>{headcount._count.users}</td>)
})
}
</tr>
</tfoot>
</table>
</div>
</div> </div>
</main> </main>
</>) </>)

View file

@ -38,17 +38,17 @@ export default function Dash() {
<div className="p-5"> <div className="p-5">
<div className="flex gap-2"> <div className="flex gap-2">
<div className="border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4 bg-gray-300"> <div className="border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4 bg-gray-300">
<div className="text-center text-2xl font-bold"></div> <div className="text-center text-2xl font-bold"></div>
<div className="flex-grow flex items-center"> <div className="flex-grow flex items-center">
<div className="text-center text-6xl font-bold">{attendTime.data}</div> <div className="text-center text-6xl font-bold">{attendTime.data}</div>
</div> </div>
</div> </div>
<div className="border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4 bg-gray-300"> {/* <div className="border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4 bg-gray-300">
<div className="text-center text-xl font-bold"></div> <div className="text-center text-xl font-bold"></div>
<div className="flex-grow flex items-center"> <div className="flex-grow flex items-center">
<div className="text-center text-6xl font-bold">{actualAttendTime.data?.toFixed(1)}</div> <div className="text-center text-6xl font-bold">{actualAttendTime.data?.toFixed(1)}</div>
</div> </div>
</div> </div> */}
<div className={[ <div className={[
"border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4", "border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-4",
(actualAttendTime.data ?? 0) + (attendTime.data ?? 0) >= 95 ? "bg-emerald-500" : "bg-red-300" (actualAttendTime.data ?? 0) + (attendTime.data ?? 0) >= 95 ? "bg-emerald-500" : "bg-red-300"
@ -97,7 +97,8 @@ export default function Dash() {
<tr className="*:p-1 *:border" key={date}> <tr className="*:p-1 *:border" key={date}>
<td>{date}</td> <td>{date}</td>
{ {
new Date(date).setHours(0, 0, 0, 0) > new Date().getTime() ? ( // FIXME: I am temporaily overriding attendance
// new Date(date).setHours(0, 0, 0, 0) > new Date().getTime() ? (
timePeriods.data?.map((timePeriod) => { timePeriods.data?.map((timePeriod) => {
const thisPeriodId = periods.data![date]![periodCnt]?.id!; const thisPeriodId = periods.data![date]![periodCnt]?.id!;
if (periods.data![date]![periodCnt]?.timePeriodId == timePeriod.id) { if (periods.data![date]![periodCnt]?.timePeriodId == timePeriod.id) {
@ -121,43 +122,43 @@ export default function Dash() {
return <td key={timePeriod.id * periodCnt} className="bg-gray-400">N/A</td> return <td key={timePeriod.id * periodCnt} className="bg-gray-400">N/A</td>
} }
}) })
) : ( // ) : (
timePeriods.data?.map((timePeriod) => { // timePeriods.data?.map((timePeriod) => {
periodCnt++; // periodCnt++;
let data = ""; // let data = "";
const thisPeriodStart = new Date(date); // const thisPeriodStart = new Date(date);
thisPeriodStart.setHours(parseInt(timePeriod.start.split(":")[0]!), parseInt(timePeriod.start.split(":")[1]!), 0, 0) // thisPeriodStart.setHours(parseInt(timePeriod.start.split(":")[0]!), parseInt(timePeriod.start.split(":")[1]!), 0, 0)
const thisPeriodEnd = new Date(date); // const thisPeriodEnd = new Date(date);
thisPeriodEnd.setHours(parseInt(timePeriod.end.split(":")[0]!), parseInt(timePeriod.end.split(":")[1]!), 0, 0) // thisPeriodEnd.setHours(parseInt(timePeriod.end.split(":")[0]!), parseInt(timePeriod.end.split(":")[1]!), 0, 0)
for (let i = attCnt; i < (myAttendance.data ?? []).length; i++) { // for (let i = attCnt; i < (myAttendance.data ?? []).length; i++) {
const thisAtt = myAttendance.data![i]; // const thisAtt = myAttendance.data![i];
if (thisAtt?.datetime! < thisPeriodStart) continue; // if (thisAtt?.datetime! < thisPeriodStart) continue;
if (thisAtt?.datetime! > thisPeriodEnd) { // if (thisAtt?.datetime! > thisPeriodEnd) {
attCnt = i; // attCnt = i;
break; // break;
} // }
if (!entered) { // if (!entered) {
data += `${data !== "" ? " / " : ""}${thisAtt?.datetime!.toLocaleTimeString()} ~ `; // data += `${data !== "" ? " / " : ""}${thisAtt?.datetime!.toLocaleTimeString()} ~ `;
} else { // } else {
data += `${thisAtt?.datetime!.toLocaleTimeString()}`; // data += `${thisAtt?.datetime!.toLocaleTimeString()}`;
} // }
entered = !entered; // entered = !entered;
} // }
if (entered && data === "" && periodCnt !== timePeriods.data!.length) { // if (entered && data === "" && periodCnt !== timePeriods.data!.length) {
return <td key={timePeriod.id * periodCnt} className="bg-green-700 text-white"></td> // return <td key={timePeriod.id * periodCnt} className="bg-green-700 text-white"></td>
} // }
if (entered && periodCnt === timePeriods.data!.length) { // if (entered && periodCnt === timePeriods.data!.length) {
return <td key={timePeriod.id * periodCnt} className="bg-yellow-700 text-white">{data}</td> // return <td key={timePeriod.id * periodCnt} className="bg-yellow-700 text-white">{data}</td>
} // }
if (data === "") { // if (data === "") {
return <td key={timePeriod.id * periodCnt} className="bg-gray-500 text-white">Absent</td> // return <td key={timePeriod.id * periodCnt} className="bg-gray-500 text-white">Absent</td>
} // }
return <td key={timePeriod.id * periodCnt} className="bg-green-700 text-white">{data}</td> // return <td key={timePeriod.id * periodCnt} className="bg-green-700 text-white">{data}</td>
}) // })
) // )
} }
</tr> </tr>
) )

View file

@ -5,7 +5,7 @@ import { adminProcedure, createTRPCRouter, loggedInProcedure, publicProcedure }
import { AddAttendance, AddPeriods, AddTimePeriodSchema, AddUserSchema, ChgPasswordSchema, DateRange, LoginSchema, PublicUserType } from "~/utils/types"; import { AddAttendance, AddPeriods, AddTimePeriodSchema, AddUserSchema, ChgPasswordSchema, DateRange, LoginSchema, PublicUserType } from "~/utils/types";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { z } from "zod"; import { z } from "zod";
import { Period } from "@prisma/client"; import { Period, TimePeriod } from "@prisma/client";
import { lastGotRfid, rfidAttendance, setRfidAttendance } from "~/utils/rfid"; import { lastGotRfid, rfidAttendance, setRfidAttendance } from "~/utils/rfid";
import { actualAttendTime, attendTime as selectedAttendTime, toggleAttendance } from "./time-sel"; import { actualAttendTime, attendTime as selectedAttendTime, toggleAttendance } from "./time-sel";
@ -200,13 +200,32 @@ export const adminRouter = createTRPCRouter({
getPeriods: loggedInProcedure getPeriods: loggedInProcedure
.query(async ({ ctx }) => { .query(async ({ ctx }) => {
return await ctx.db.period.findMany({ return await ctx.db.period.findMany({
include: {
timePeriod: true
},
orderBy: [{ orderBy: [{
date: "asc", date: "asc",
}, { }, {
timePeriodId: "asc", timePeriodId: "asc",
}], }],
}).then((periods) => { }).then((periods) => {
const groupedPeriods: { [key: string]: Period[] } = {}; const groupedPeriods: { [key: string]: ({
timePeriod: {
id: number;
name: string;
start: string;
end: string;
createdAt: Date;
updatedAt: Date;
};
} & {
id: number;
date: Date;
timePeriodId: number;
collecting: boolean;
createdAt: Date;
updatedAt: Date;
})[]} = {};
periods.forEach((period) => { periods.forEach((period) => {
const dateStr = period.date.toLocaleDateString(); const dateStr = period.date.toLocaleDateString();
if (groupedPeriods[dateStr] === undefined) { if (groupedPeriods[dateStr] === undefined) {
@ -402,6 +421,55 @@ export const adminRouter = createTRPCRouter({
message: "RFID attendance toggled.", message: "RFID attendance toggled.",
}; };
}), }),
getAllRoster: adminProcedure
.query(async ({ ctx }) => {
return await ctx.db.user.findMany({
select: {
id: true,
name: true,
grade: true,
class: true,
number: true,
periods: {
select: {
id: true,
},
},
},
orderBy: [
{
grade: "asc",
},
{
class: "asc",
},
{
number: "asc",
},
]
})
}),
getAllPeriodsHeadcount: adminProcedure
.query(async ({ ctx }) => {
return await ctx.db.period.findMany({
select: {
id: true,
_count: {
select: {
users: true,
}
}
},
orderBy: [
{
date: "asc",
},
{
timePeriodId: "asc",
},
]
})
}),
getRoster: adminProcedure getRoster: adminProcedure
.input(z.object({ date: z.date()})) .input(z.object({ date: z.date()}))
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
@ -415,6 +483,7 @@ export const adminRouter = createTRPCRouter({
}, },
select: { select: {
id: true, id: true,
username: true,
name: true, name: true,
grade: true, grade: true,
class: true, class: true,

View file

@ -16,11 +16,12 @@ export const attendTime = async (ctx: Context, username: string) => {
} }
} }
}, },
where: { // FIXME: I am temporaily overriding attendance
date: { // where: {
gte: new Date(new Date().setHours(23, 59, 59, 999)), // date: {
} // gte: new Date(new Date().setHours(23, 59, 59, 999)),
} // }
// }
} }
} }
}); });