adding scheduler

This commit is contained in:
2026-09-18 12:27:51 -04:00
parent 256e87c8c8
commit f4f3f76d6c
7 changed files with 273 additions and 35 deletions
+136 -2
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import type { FormEvent } from "react";
import { getCompanies } from "@api/companiesApi";
import { getWeekSchedule } from "@api/scheduleApi";
import { clockIn, clockOut, getWeekSchedule } from "@api/scheduleApi";
import { createUser, getUsers, updateUserPreference } from "@api/usersApi";
import { Modal } from "@components/Modal";
import { PageHeader } from "@components/PageHeader";
@@ -66,6 +66,9 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
const [error, setError] = useState("");
const [isAddUserOpen, setIsAddUserOpen] = useState(false);
const [isPreferenceOpen, setIsPreferenceOpen] = useState(false);
const [checkingInUser, setCheckingInUser] = useState<Employee | null>(null);
const [checkInDateTime, setCheckInDateTime] = useState("");
const [savingActionUserId, setSavingActionUserId] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
@@ -153,6 +156,69 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
}
}
function getDayOfWeekFromDateTime(value: string): import("@/types/users").DayOfWeek {
const days: import("@/types/users").DayOfWeek[] = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
return days[new Date(value).getDay()];
}
function openCheckInModal(user: Employee) {
const now = new Date();
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000)
.toISOString()
.slice(0, 16);
setCheckingInUser(user);
setCheckInDateTime(local);
}
async function handleClockIn(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!checkingInUser || !checkInDateTime) {
return;
}
setError("");
setSavingActionUserId(checkingInUser.id);
try {
await clockIn(backendPreference, checkingInUser.id, {
day_of_week: getDayOfWeekFromDateTime(checkInDateTime),
enter_time: new Date(checkInDateTime).toISOString(),
});
setCheckingInUser(null);
await loadUsersPage();
} catch (saveError) {
setError(saveError instanceof Error ? saveError.message : "Unable to check in user");
} finally {
setSavingActionUserId(null);
}
}
async function handleClockOut(userId: number, activeSchedule: Schedule) {
setError("");
setSavingActionUserId(userId);
try {
await clockOut(backendPreference, userId, {
day_of_week: activeSchedule.day_of_week,
out_time: new Date().toISOString(),
});
await loadUsersPage();
} catch (saveError) {
setError(saveError instanceof Error ? saveError.message : "Unable to check out user");
} finally {
setSavingActionUserId(null);
}
}
return (
<>
<PageHeader
@@ -191,12 +257,16 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
<th>Check in</th>
<th>Check out</th>
<th>Days worked</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{users.map((user) => {
const userSchedules = schedulesByUser[user.id] ?? [];
const status = getUserStatus(userSchedules);
const activeSchedule = userSchedules.find(
(schedule) => schedule.enter_time && !schedule.out_time,
);
const latestSchedule = userSchedules.at(-1);
const company = companies.find((item) => item.id === user.company_id);
const daysWorked = new Set(
@@ -210,11 +280,15 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
className={selectedUserId === user.id ? styles.selectedRow : ""}
key={user.id}
onClick={() => setSelectedUserId(user.id)}
title="Double click to edit user information"
>
<td>
{user.name} {user.last_name}
</td>
<td>{company?.name ?? `Company #${user.company_id}`}</td>
<td>
{company?.name ??
(user.company_id ? `Company #${user.company_id}` : "Unassigned")}
</td>
<td>{user.preference}</td>
<td>
<span className={`${styles.status} ${styles[status]}`}>
@@ -224,6 +298,35 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
<td>{formatDateTime(latestSchedule?.enter_time ?? null)}</td>
<td>{formatDateTime(latestSchedule?.out_time ?? null)}</td>
<td>{daysWorked}</td>
<td>
<div className={styles.actionGroup}>
<button
className={styles.actionButton}
disabled={Boolean(activeSchedule) || savingActionUserId === user.id}
onClick={(event) => {
event.stopPropagation();
openCheckInModal(user);
}}
type="button"
>
Check in
</button>
<button
className={styles.actionButton}
disabled={!activeSchedule || savingActionUserId === user.id}
onClick={(event) => {
event.stopPropagation();
if (activeSchedule) {
void handleClockOut(user.id, activeSchedule);
}
}}
type="button"
>
Check out
</button>
</div>
</td>
</tr>
);
})}
@@ -314,6 +417,37 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
</form>
</Modal>
) : null}
{checkingInUser ? (
<Modal
title={`Check in ${checkingInUser.name} ${checkingInUser.last_name}`}
onClose={() => setCheckingInUser(null)}
>
<form className={styles.form} onSubmit={handleClockIn}>
<label>
Date and time
<input
type="datetime-local"
value={checkInDateTime}
onChange={(event) => setCheckInDateTime(event.target.value)}
required
/>
</label>
<p className={styles.muted}>
Day of week will be derived as{" "}
{checkInDateTime ? getDayOfWeekFromDateTime(checkInDateTime) : "--"} from the selected
date.
</p>
<button
className={styles.primaryButton}
disabled={savingActionUserId === checkingInUser.id}
type="submit"
>
{savingActionUserId === checkingInUser.id ? "Saving..." : "Check in"}
</button>
</form>
</Modal>
) : null}
</>
);
}