adding scheduler
This commit is contained in:
+136
-2
@@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user