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) &&
+
+
+
機器人研究社 Build Season 總表
+
+
列印時間: {genDate.toLocaleString()}
+
+
+
+
+ 年級 |
+ 班級 |
+ 座號 |
+ 姓名 |
+ {
+ Object.keys(periods.data ?? {}).map((date) => {
+ return ({date} | )
+ })
+ }
+
+
+ {
+ Object.keys(periods.data ?? {}).map((date) => {
+ return periods.data![date]!.map((period) => {
+ return ({period.timePeriod.name} | )
+ })
+ })
+ }
+
+
+
+ {
+ allRoster.data?.map((user) => {
+ return (
+ {user.grade} |
+ {user.class} |
+ {user.number} |
+ {user.name} |
+ {
+ 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 ({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() {
-
+ {/*
-
已選取時數
+
時數
@@ -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() {
{date} |
{
- 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 N/A |
}
})
- ) : (
- 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 {data} |
- }
- if (data === "") {
- return Absent |
- }
- return {data} |
- })
- )
+ // if (entered && data === "" && periodCnt !== timePeriods.data!.length) {
+ // return |
+ // }
+ // if (entered && periodCnt === timePeriods.data!.length) {
+ // return {data} |
+ // }
+ // if (data === "") {
+ // return Absent |
+ // }
+ // return {data} |
+ // })
+ // )
}
)
@@ -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) {