diff --git a/docker-compose.yml b/docker-compose.yml index fa1c468..4e5e6cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,9 +14,9 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres DATABASE_URL: postgresql://postgres:postgres@postgres:5432/savage_tracking?schema=public - JWT_SECRET: secret + JWT_SECRET: "CHANGE_THIS" TZ: Asia/Taipei - RFID_PKEY: secret + RFID_PKEY: "CHANGE_THIS" depends_on: postgres: condition: service_healthy diff --git a/src/pages/dash/admin/roster.tsx b/src/pages/dash/admin/roster.tsx index 791055a..4e3aaa0 100644 --- a/src/pages/dash/admin/roster.tsx +++ b/src/pages/dash/admin/roster.tsx @@ -8,9 +8,10 @@ import { api } from "~/utils/api"; export default function Dash() { const { push } = useRouter(); 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: bigToPdf, targetRef: bigPdfRef } = usePDF({ filename: `Build_Season_總表.pdf` }); const isLoggedIn = api.admin.isLoggedIn.useQuery(); const periods = api.admin.getPeriods.useQuery(); @@ -18,7 +19,9 @@ export default function Dash() { const roster = api.admin.getRoster.useQuery({ date: date }); - + const allRoster = api.admin.getAllRoster.useQuery(); + const allPeriodsHeadcount = api.admin.getAllPeriodsHeadcount.useQuery(); + useEffect(() => { if (isLoggedIn.failureCount > 0) { push("/"); @@ -60,6 +63,16 @@ export default function Dash() { }}> Download PDF + { (roster.isLoading) &&
+ @@ -95,6 +109,7 @@ export default function Dash() { { roster.data?.map((user) => { return ( + @@ -102,9 +117,9 @@ export default function Dash() { { timePeriods.data?.map((timePeriod) => { if (user.periods.find((period) => period.timePeriod.id === timePeriod.id)) { - return () + return () } else { - return () + return () } }) } @@ -114,7 +129,74 @@ export default function Dash() {
學號 年級 班級 座號
{user.username} {user.grade} {user.class} {user.number}
+
+
+

機器人研究社 Build Season 總表

+
+

列印時間: {genDate.toLocaleString()}

+
+ + + + + + + + { + Object.keys(periods.data ?? {}).map((date) => { + return () + }) + } + + + { + Object.keys(periods.data ?? {}).map((date) => { + return periods.data![date]!.map((period) => { + return () + }) + }) + } + + + + { + allRoster.data?.map((user) => { + return ( + + + + + { + Object.keys(periods.data ?? {}).map((date) => { + return periods.data![date]!.map((thisPeriod) => { + if (user.periods.find((period) => period.id === thisPeriod.id)) { + return () + } else { + return () + } + }) + }) + } + ) + }) + } + + + + + { + allPeriodsHeadcount.data?.map((headcount) => { + return () + }) + } + + +
年級班級座號姓名{date}
{period.timePeriod.name}
{user.grade}{user.class}{user.number}{user.name}
總計{headcount._count.users}
+
+ ) -} \ No newline at end of file +} diff --git a/src/pages/dash/index.tsx b/src/pages/dash/index.tsx index 8409f7b..be7b7dd 100644 --- a/src/pages/dash/index.tsx +++ b/src/pages/dash/index.tsx @@ -36,9 +36,9 @@ export default function Dash() {
-
+ {/*
-
已選取時數
+
時數
{attendTime.data}
@@ -58,25 +58,20 @@ export default function Dash() {
{((actualAttendTime.data ?? 0) + (attendTime.data ?? 0)).toFixed(1)}
-
+
*/}
  • 上午時段 (早) 為 09:00 ~ 12:00
  • -
      -
    • 為獎勵上午時段到校,實到 3 小時以 4 小時計算時數
    • -
  • 下午時段 (午) 為 13:00 ~ 16:00
  • 晚間時段 (晚) 為 16:00 ~ 19:00

    -
  • 1/16/2024 為正常社課時間,不列入時數計算
  • -
  • 1/17/2024、1/18/2024 開放時段為放學後 17:00 ~ 19:30 (請直接選擇 [晚] 時段)
  • -
  • 1/19/2024 開放時間為結業式後 12:00 ~ 17:00 (請直接選擇 [午] 時段)
  • -
  • 1/20/2024 ~ 2/7/2024 開放時間為 09:00 ~ 19:00
  • +
  • 暑假開放檢修非強制參加也沒有最低時數限制
  • +
  • 本系統開放填寫期限為 07/19 18:00
-
請點選下方藍色方塊切換狀態 (> 95 小時會變綠燈)
+
請點選下方藍色方塊切換狀態
@@ -97,7 +92,8 @@ export default function Dash() { { - 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) => { const thisPeriodId = periods.data![date]![periodCnt]?.id!; if (periods.data![date]![periodCnt]?.timePeriodId == timePeriod.id) { @@ -121,43 +117,43 @@ export default function Dash() { return } }) - ) : ( - timePeriods.data?.map((timePeriod) => { - periodCnt++; + // ) : ( + // timePeriods.data?.map((timePeriod) => { + // periodCnt++; - let data = ""; - const thisPeriodStart = new Date(date); - thisPeriodStart.setHours(parseInt(timePeriod.start.split(":")[0]!), parseInt(timePeriod.start.split(":")[1]!), 0, 0) - const thisPeriodEnd = new Date(date); - thisPeriodEnd.setHours(parseInt(timePeriod.end.split(":")[0]!), parseInt(timePeriod.end.split(":")[1]!), 0, 0) + // let data = ""; + // const thisPeriodStart = new Date(date); + // thisPeriodStart.setHours(parseInt(timePeriod.start.split(":")[0]!), parseInt(timePeriod.start.split(":")[1]!), 0, 0) + // const thisPeriodEnd = new Date(date); + // thisPeriodEnd.setHours(parseInt(timePeriod.end.split(":")[0]!), parseInt(timePeriod.end.split(":")[1]!), 0, 0) - for (let i = attCnt; i < (myAttendance.data ?? []).length; i++) { - const thisAtt = myAttendance.data![i]; - if (thisAtt?.datetime! < thisPeriodStart) continue; - if (thisAtt?.datetime! > thisPeriodEnd) { - attCnt = i; - break; - } - if (!entered) { - data += `${data !== "" ? " / " : ""}${thisAtt?.datetime!.toLocaleTimeString()} ~ `; - } else { - data += `${thisAtt?.datetime!.toLocaleTimeString()}`; - } - entered = !entered; - } + // for (let i = attCnt; i < (myAttendance.data ?? []).length; i++) { + // const thisAtt = myAttendance.data![i]; + // if (thisAtt?.datetime! < thisPeriodStart) continue; + // if (thisAtt?.datetime! > thisPeriodEnd) { + // attCnt = i; + // break; + // } + // if (!entered) { + // data += `${data !== "" ? " / " : ""}${thisAtt?.datetime!.toLocaleTimeString()} ~ `; + // } else { + // data += `${thisAtt?.datetime!.toLocaleTimeString()}`; + // } + // entered = !entered; + // } - if (entered && data === "" && periodCnt !== timePeriods.data!.length) { - return - } - if (entered && periodCnt === timePeriods.data!.length) { - return - } - if (data === "") { - return - } - return - }) - ) + // if (entered && data === "" && periodCnt !== timePeriods.data!.length) { + // return + // } + // if (entered && periodCnt === timePeriods.data!.length) { + // return + // } + // if (data === "") { + // return + // } + // return + // }) + // ) } ) @@ -168,4 +164,4 @@ export default function Dash() { ) -} \ No newline at end of file +} diff --git a/src/server/api/routers/admin.ts b/src/server/api/routers/admin.ts index 6002d0a..e6a8a4e 100644 --- a/src/server/api/routers/admin.ts +++ b/src/server/api/routers/admin.ts @@ -5,7 +5,7 @@ import { adminProcedure, createTRPCRouter, loggedInProcedure, publicProcedure } import { AddAttendance, AddPeriods, AddTimePeriodSchema, AddUserSchema, ChgPasswordSchema, DateRange, LoginSchema, PublicUserType } from "~/utils/types"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { Period } from "@prisma/client"; +import { Period, TimePeriod } from "@prisma/client"; import { lastGotRfid, rfidAttendance, setRfidAttendance } from "~/utils/rfid"; import { actualAttendTime, attendTime as selectedAttendTime, toggleAttendance } from "./time-sel"; @@ -200,13 +200,32 @@ export const adminRouter = createTRPCRouter({ getPeriods: loggedInProcedure .query(async ({ ctx }) => { return await ctx.db.period.findMany({ + include: { + timePeriod: true + }, orderBy: [{ date: "asc", }, { timePeriodId: "asc", }], }).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) => { const dateStr = period.date.toLocaleDateString(); if (groupedPeriods[dateStr] === undefined) { @@ -402,6 +421,55 @@ export const adminRouter = createTRPCRouter({ 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 .input(z.object({ date: z.date()})) .query(async ({ input, ctx }) => { @@ -415,6 +483,7 @@ export const adminRouter = createTRPCRouter({ }, select: { id: true, + username: true, name: true, grade: true, class: true, diff --git a/src/server/api/routers/time-sel.ts b/src/server/api/routers/time-sel.ts index 18a7bd5..6a48ef2 100644 --- a/src/server/api/routers/time-sel.ts +++ b/src/server/api/routers/time-sel.ts @@ -16,11 +16,12 @@ export const attendTime = async (ctx: Context, username: string) => { } } }, - where: { - date: { - gte: new Date(new Date().setHours(23, 59, 59, 999)), - } - } + // FIXME: I am temporaily overriding attendance + // where: { + // date: { + // gte: new Date(new Date().setHours(23, 59, 59, 999)), + // } + // } } } }); @@ -119,8 +120,8 @@ export const toggleAttendance = async (ctx: Context, input: { periodId: number, if (!period) throw new TRPCError({ code: "NOT_FOUND", message: "Period not found" }); if (checkWeekInAdvance) { - const weekInAdvance = new Date("2024/01/16 23:59"); - if (new Date() > weekInAdvance) throw new TRPCError({ code: "BAD_REQUEST", message: "Time selection period has passed. Please contact HR." }); + const weekInAdvance = new Date("2024/07/19 18:00"); + if (new Date() > weekInAdvance || period.date < weekInAdvance) throw new TRPCError({ code: "BAD_REQUEST", message: "Time selection period has passed. Please contact HR." }); } if (input.attendance) {
{date}N/A{data}Absent{data}{data}Absent{data}