mirror of
https://github.com/aaronleetw/savage-tracking.git
synced 2024-11-14 11:01:39 -08:00
added user select period
This commit is contained in:
parent
54cdb7e83f
commit
b5f7f9ef3c
10 changed files with 236 additions and 49 deletions
|
@ -14,3 +14,4 @@
|
|||
DATABASE_URL="file:./db.sqlite"
|
||||
JWT_SECRET="well, now the secret is spoiled, isn't it?"
|
||||
TZ="Asia/Taipei"
|
||||
RFID_PKEY="secret"
|
|
@ -54,7 +54,10 @@ export default function Periods() {
|
|||
if (periods.data![date]![periodCnt]?.timePeriodId == timePeriod.id) {
|
||||
periodCnt++;
|
||||
return (<td key={timePeriod.id * periodCnt} className="bg-emerald-600 text-white hover:cursor-pointer"
|
||||
onClick={() => disablePeriod.mutateAsync(periods.data![date]![thisPeriodCnt]!.id!).then(() => periods.refetch())}>
|
||||
onClick={() => disablePeriod.mutateAsync({
|
||||
date: date,
|
||||
timePeriodId: periods.data![date]![thisPeriodCnt]!.timePeriodId
|
||||
}).then(() => periods.refetch())}>
|
||||
Disable
|
||||
</td>)
|
||||
} else {
|
||||
|
@ -73,6 +76,7 @@ export default function Periods() {
|
|||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-red-500 font-bold mb-5">WARNING: Disabling periods will cause records to be deleted as well!</p>
|
||||
<hr />
|
||||
<h2 className="text-xl font-bold mt-5">Add Date</h2>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { z } from 'zod'
|
||||
import { appRouter } from '~/server/api/root'
|
||||
import { setLastRfid } from '~/utils/rfid'
|
||||
import { db } from '~/server/db'
|
||||
|
||||
type ResponseData = {
|
||||
message: string
|
||||
|
@ -12,15 +14,16 @@ export default function handler(
|
|||
) {
|
||||
if ("uid" in req.body) {
|
||||
try {
|
||||
z.string().regex(/^[0-9a-f]{8}$/i).parse(req.body.uid)
|
||||
z.string().regex(/^[0-9a-f]{8}$/i).parse(req.body.uid);
|
||||
} catch (e) {
|
||||
res.status(200).json({ message: 'Invalid UID!' })
|
||||
return
|
||||
res.status(200).json({ message: 'Invalid UID!' });
|
||||
return;
|
||||
}
|
||||
setLastRfid(req.body.uid)
|
||||
res.status(200).json({ message: 'Received!' })
|
||||
setLastRfid(req.body.uid);
|
||||
const caller = appRouter.createCaller({ session: undefined, db, req, res });
|
||||
res.status(200).json({ message: 'Received!' });
|
||||
}
|
||||
else {
|
||||
res.status(200).json({ message: 'No UID!' })
|
||||
res.status(200).json({ message: 'No UID!' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ export default function Dash() {
|
|||
const users = api.admin.getAllUsers.useQuery();
|
||||
const lastRfid = api.admin.getLastRfid.useQuery();
|
||||
const setUserRfid = api.admin.setUserRfid.useMutation();
|
||||
const rfidAttendance = api.admin.rfidAttendance.useQuery();
|
||||
const toggleRfidAttendance = api.admin.toggleRfidAttendance.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn.failureCount > 0) {
|
||||
|
@ -37,6 +39,15 @@ export default function Dash() {
|
|||
<main className="">
|
||||
<DashboardHeader url="/dash/admin/rfid" />
|
||||
<div className="m-5">
|
||||
RFID Attendance: {rfidAttendance.data?.rfidAttendance ? "Enabled" : "Disabled"}
|
||||
<button className={[
|
||||
"p-1 px-2 text-lg font-bold rounded mb-2 ml-2 text-white transition-colors",
|
||||
rfidAttendance.data?.rfidAttendance ? "bg-yellow-600 hover:bg-yellow-700" : "bg-green-600 hover:bg-green-700"
|
||||
].join(" ")} onClick={() => toggleRfidAttendance.mutateAsync()
|
||||
.then(() => rfidAttendance.refetch())
|
||||
.catch((err) => console.log(err))}>
|
||||
{rfidAttendance.data?.rfidAttendance ? "Disable" : "Enable"}
|
||||
</button>
|
||||
<table className="table-auto border-collapse border-2 border-black w-fit">
|
||||
<thead>
|
||||
<tr className="*:p-1 *:border border-b-2 border-b-black">
|
||||
|
@ -60,6 +71,7 @@ export default function Dash() {
|
|||
<td>{user.name}</td>
|
||||
<td>{user.rfid}</td>
|
||||
<td className="text-emerald-600 underline text-center hover:cursor-pointer" onClick={() => {
|
||||
if (lastRfid.data === "") return;
|
||||
setUserRfid.mutateAsync({
|
||||
username: user.username,
|
||||
rfid: lastRfid.data || ""
|
||||
|
|
|
@ -7,7 +7,12 @@ import { api } from "~/utils/api";
|
|||
export default function Dash() {
|
||||
const { push } = useRouter();
|
||||
const isLoggedIn = api.admin.isLoggedIn.useQuery();
|
||||
|
||||
const periods = api.admin.getPeriods.useQuery();
|
||||
const timePeriods = api.admin.getTimePeriods.useQuery();
|
||||
const mySelectedPeriods = api.timeSel.getMySelectedPeriods.useQuery();
|
||||
const toggleAttendance = api.timeSel.toggleAttendance.useMutation();
|
||||
const attendTime = api.timeSel.attendTime.useQuery();
|
||||
let periodCnt = 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn.failureCount > 0) {
|
||||
|
@ -25,6 +30,67 @@ export default function Dash() {
|
|||
</Head>
|
||||
<main className="">
|
||||
<DashboardHeader url="/dash" />
|
||||
<div className="p-5">
|
||||
<div className={[
|
||||
"border rounded-xl h-32 w-40 p-2 flex items-center justify-center flex-col mb-5",
|
||||
(attendTime.data || 0) >= 30 ? "bg-emerald-500" : "bg-red-300"
|
||||
].join(" ")}>
|
||||
<div className="text-center text-3xl font-bold">Hours</div>
|
||||
<div className="flex-grow flex items-center">
|
||||
<div className="text-center text-6xl font-bold">{attendTime.data}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold mb-2">Click cells below to toggle</div>
|
||||
<table className="table-auto border-collapse border-2 border-black w-fit mb-5">
|
||||
<thead>
|
||||
<tr className="*:p-1 *:border border-b-2 border-b-black">
|
||||
<th>Date</th>
|
||||
{
|
||||
timePeriods.data?.map((timePeriods) => {
|
||||
return (<th key={timePeriods.id}>{timePeriods.name} ({timePeriods.start} ~ {timePeriods.end})</th>)
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
Object.keys(periods.data || {}).map((date) => {
|
||||
periodCnt = 0;
|
||||
return (
|
||||
<tr className="*:p-1 *:border" key={date}>
|
||||
<td>{date}</td>
|
||||
{
|
||||
timePeriods.data?.map((timePeriod) => {
|
||||
const thisPeriodId = periods.data![date]![periodCnt]?.id!;
|
||||
if (periods.data![date]![periodCnt]?.timePeriodId == timePeriod.id) {
|
||||
periodCnt++;
|
||||
if (mySelectedPeriods.data?.findIndex((period) => period == thisPeriodId) != -1) {
|
||||
return <td key={timePeriod.id * periodCnt} className="bg-emerald-200 hover:cursor-pointer"
|
||||
onClick={() => toggleAttendance.mutateAsync({
|
||||
periodId: thisPeriodId || -1,
|
||||
attendance: false
|
||||
}).then(() => mySelectedPeriods.refetch()).then(() => attendTime.refetch())}>
|
||||
Will Attend
|
||||
</td>
|
||||
} else {
|
||||
return <td key={timePeriod.id * periodCnt} className="bg-sky-100 hover:cursor-pointer"
|
||||
onClick={() => toggleAttendance.mutateAsync({
|
||||
periodId: thisPeriodId || -1,
|
||||
attendance: true
|
||||
}).then(() => mySelectedPeriods.refetch()).then(() => attendTime.refetch())}></td>
|
||||
}
|
||||
} else {
|
||||
return <td key={timePeriod.id * periodCnt} className="bg-gray-400">N/A</td>
|
||||
}
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</>)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { postRouter } from "~/server/api/routers/post";
|
||||
import { timeSelRouter } from "~/server/api/routers/time-sel";
|
||||
import { createTRPCRouter } from "~/server/api/trpc";
|
||||
import { adminRouter } from "./routers/admin";
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { adminRouter } from "./routers/admin";
|
|||
* All routers added in /api/routers should be manually added here.
|
||||
*/
|
||||
export const appRouter = createTRPCRouter({
|
||||
// post: postRouter,
|
||||
timeSel: timeSelRouter,
|
||||
admin: adminRouter,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { AddPeriods, AddTimePeriodSchema, AddUserSchema, ChgPasswordSchema, Logi
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { Period } from "@prisma/client";
|
||||
import { lastGotRfid } from "~/utils/rfid";
|
||||
import { lastGotRfid, rfidAttendance, setRfidAttendance } from "~/utils/rfid";
|
||||
|
||||
export const adminRouter = createTRPCRouter({
|
||||
isLoggedIn: loggedInProcedure.query(() => true),
|
||||
|
@ -220,7 +220,18 @@ export const adminRouter = createTRPCRouter({
|
|||
addPeriods: adminProcedure
|
||||
.input(AddPeriods)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await ctx.db.timePeriod.findMany().then(
|
||||
const period = await ctx.db.period.findFirst({
|
||||
where: {
|
||||
date: new Date(input.date),
|
||||
},
|
||||
});
|
||||
if (period !== null) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Periods with date already exist.",
|
||||
})
|
||||
}
|
||||
return await ctx.db.timePeriod.findMany().then(
|
||||
async (timePeriods) => {
|
||||
timePeriods.forEach(async (timePeriod) => {
|
||||
await ctx.db.period.create({
|
||||
|
@ -249,6 +260,16 @@ export const adminRouter = createTRPCRouter({
|
|||
timePeriodId: z.number().int(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
// if data exists, do nothing
|
||||
const period = await ctx.db.period.findFirst({
|
||||
where: {
|
||||
date: new Date(input.date),
|
||||
timePeriodId: input.timePeriodId,
|
||||
},
|
||||
});
|
||||
if (period !== null) {
|
||||
return period;
|
||||
}
|
||||
return await ctx.db.period.create({
|
||||
data: {
|
||||
date: new Date(input.date),
|
||||
|
@ -257,11 +278,15 @@ export const adminRouter = createTRPCRouter({
|
|||
})
|
||||
}),
|
||||
disablePeriod: adminProcedure
|
||||
.input(z.number().int())
|
||||
.input(z.object({
|
||||
date: z.string(),
|
||||
timePeriodId: z.number().int(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await ctx.db.period.delete({
|
||||
return await ctx.db.period.deleteMany({
|
||||
where: {
|
||||
id: input,
|
||||
date: new Date(input.date),
|
||||
timePeriodId: input.timePeriodId,
|
||||
},
|
||||
})
|
||||
}),
|
||||
|
@ -284,4 +309,19 @@ export const adminRouter = createTRPCRouter({
|
|||
},
|
||||
})
|
||||
}),
|
||||
rfidAttendance: adminProcedure
|
||||
.query(() => {
|
||||
return {
|
||||
status: "success",
|
||||
rfidAttendance: rfidAttendance,
|
||||
}
|
||||
}),
|
||||
toggleRfidAttendance: adminProcedure
|
||||
.mutation(() => {
|
||||
setRfidAttendance(!rfidAttendance);
|
||||
return {
|
||||
status: "success",
|
||||
message: "RFID attendance toggled.",
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
|
||||
export const postRouter = createTRPCRouter({
|
||||
hello: publicProcedure
|
||||
.input(z.object({ text: z.string() }))
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input.text}`,
|
||||
};
|
||||
}),
|
||||
|
||||
create: publicProcedure
|
||||
.input(z.object({ name: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// simulate a slow db call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
return ctx.db.post.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
getLatest: publicProcedure.query(({ ctx }) => {
|
||||
return ctx.db.post.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
}),
|
||||
});
|
89
src/server/api/routers/time-sel.ts
Normal file
89
src/server/api/routers/time-sel.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createTRPCRouter, loggedInProcedure, publicProcedure } from "~/server/api/trpc";
|
||||
|
||||
export const timeSelRouter = createTRPCRouter({
|
||||
getMySelectedPeriods: loggedInProcedure
|
||||
.query(async ({ ctx }) => {
|
||||
const user = await ctx.db.user.findUnique({
|
||||
where: { username: ctx.session?.username },
|
||||
select: {
|
||||
periods: {
|
||||
select: {
|
||||
id: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!user) throw new TRPCError({ code: "UNAUTHORIZED", message: "User not found" });
|
||||
return user.periods.map(period => period.id);
|
||||
}),
|
||||
toggleAttendance: loggedInProcedure
|
||||
.input(z.object({
|
||||
periodId: z.number().int(),
|
||||
attendance: z.boolean(),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
if (input.attendance) {
|
||||
await ctx.db.period.update({
|
||||
where: { id: input.periodId },
|
||||
data: {
|
||||
users: {
|
||||
connect: {
|
||||
username: ctx.session?.username,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await ctx.db.period.update({
|
||||
where: { id: input.periodId },
|
||||
data: {
|
||||
users: {
|
||||
disconnect: {
|
||||
username: ctx.session?.username,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "An error occurred." });
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
attendTime: loggedInProcedure
|
||||
.query(async ({ ctx }) => {
|
||||
const user = await ctx.db.user.findUnique({
|
||||
where: { username: ctx.session?.username },
|
||||
select: {
|
||||
periods: {
|
||||
select: {
|
||||
timePeriod: {
|
||||
select: {
|
||||
start: true,
|
||||
end: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!user) throw new TRPCError({ code: "UNAUTHORIZED", message: "User not found" });
|
||||
let total = 0;
|
||||
user.periods.forEach(period => {
|
||||
const start = new Date();
|
||||
const end = new Date();
|
||||
const [startHour, startMinute] = period.timePeriod.start.split(":");
|
||||
const [endHour, endMinute] = period.timePeriod.end.split(":");
|
||||
start.setHours(parseInt(startHour!));
|
||||
start.setMinutes(parseInt(startMinute!));
|
||||
end.setHours(parseInt(endHour!));
|
||||
end.setMinutes(parseInt(endMinute!));
|
||||
total += (end.getTime() - start.getTime()) / 1000 / 60 / 60; // convert to hours
|
||||
})
|
||||
return total;
|
||||
}),
|
||||
});
|
|
@ -2,3 +2,7 @@ export let lastGotRfid = "";
|
|||
export const setLastRfid = (uid: string) => {
|
||||
lastGotRfid = uid;
|
||||
};
|
||||
export let rfidAttendance = true;
|
||||
export const setRfidAttendance = (value: boolean) => {
|
||||
rfidAttendance = value;
|
||||
};
|
Loading…
Reference in a new issue