mirror of
https://github.com/aaronleetw/savage-tracking.git
synced 2024-11-14 19:11:39 -08:00
temporarily overriding attendance, roster fix
This commit is contained in:
parent
e02db95f55
commit
676b50db0c
4 changed files with 200 additions and 47 deletions
|
@ -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,6 +19,8 @@ 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) {
|
||||||
|
@ -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>
|
||||||
</>)
|
</>)
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)),
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue