adding schedule by company

This commit is contained in:
2026-09-18 17:13:50 -04:00
parent 1911a973a0
commit 4c39253b81
9 changed files with 1268 additions and 1839 deletions
+128 -25
View File
@@ -1,10 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
import { getWeekSchedule } from "@api/scheduleApi";
import { getUsers } from "@api/usersApi";
import { PageHeader } from "@components/PageHeader";
import type { BackendPreference } from "@/types/backend";
import type { Schedule } from "@/types/schedule";
import type { DayOfWeek, Employee } from "@/types/users";
import type { DayOfWeek } from "@/types/users";
import { useCompaniesStore } from "../store/companiesStore";
import styles from "./SchedulePage.module.scss";
type SchedulePageProps = {
@@ -25,6 +26,31 @@ function formatDay(day: DayOfWeek): string {
return day.charAt(0).toUpperCase() + day.slice(1);
}
function getWeekStart(date: Date): Date {
const weekStart = new Date(date);
weekStart.setHours(0, 0, 0, 0);
weekStart.setDate(weekStart.getDate() - weekStart.getDay());
return weekStart;
}
function addDays(date: Date, days: number): Date {
const nextDate = new Date(date);
nextDate.setDate(nextDate.getDate() + days);
return nextDate;
}
function formatDate(value: Date): string {
return value.toLocaleDateString([], {
month: "short",
day: "numeric",
year: "numeric",
});
}
function formatShortDate(value: Date): string {
return value.toLocaleDateString([], { month: "short", day: "numeric" });
}
function formatTime(value: string | null): string {
if (!value) {
return "Not registered";
@@ -35,14 +61,47 @@ function formatTime(value: string | null): string {
export function SchedulePage({ backendPreference }: SchedulePageProps) {
const [schedules, setSchedules] = useState<Schedule[]>([]);
const [users, setUsers] = useState<Employee[]>([]);
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [selectedCompany, setSelectedCompany] = useState("");
const [selectedWeekStart, setSelectedWeekStart] = useState(() => getWeekStart(new Date()));
const companyNames = useCompaniesStore((state) => state.companyNames);
const companiesError = useCompaniesStore((state) => state.error);
const loadCompanies = useCompaniesStore((state) => state.loadCompanies);
const selectedWeekEnd = useMemo(() => addDays(selectedWeekStart, 6), [selectedWeekStart]);
const selectedWeekEndExclusive = useMemo(
() => addDays(selectedWeekStart, 7),
[selectedWeekStart],
);
const selectedWeekDays = useMemo(
() => weekDays.map((day, index) => ({ day, date: addDays(selectedWeekStart, index) })),
[selectedWeekStart],
);
const schedulesByDay = useMemo(() => {
return schedules.reduce<Record<DayOfWeek, Schedule[]>>(
(accumulator, schedule) => {
accumulator[schedule.day_of_week] = [...accumulator[schedule.day_of_week], schedule];
if (selectedCompany && schedule.company !== selectedCompany) {
return accumulator;
}
if (!schedule.enter_time) {
return accumulator;
}
const enterDate = new Date(schedule.enter_time);
if (
Number.isNaN(enterDate.getTime()) ||
enterDate < selectedWeekStart ||
enterDate >= selectedWeekEndExclusive
) {
return accumulator;
}
const day = weekDays[enterDate.getDay()];
accumulator[day] = [...accumulator[day], schedule];
return accumulator;
},
{
@@ -55,7 +114,15 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
saturday: [],
},
);
}, [schedules]);
}, [schedules, selectedCompany, selectedWeekEndExclusive, selectedWeekStart]);
function showPreviousWeek() {
setSelectedWeekStart((currentWeekStart) => addDays(currentWeekStart, -7));
}
function showNextWeek() {
setSelectedWeekStart((currentWeekStart) => addDays(currentWeekStart, 7));
}
useEffect(() => {
let isMounted = true;
@@ -65,14 +132,10 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
setIsLoading(true);
try {
const [scheduleResponse, usersResponse] = await Promise.all([
getWeekSchedule(backendPreference),
getUsers(backendPreference),
]);
const scheduleResponse = await getWeekSchedule(backendPreference);
if (isMounted) {
setSchedules(scheduleResponse.schedules);
setUsers(usersResponse);
}
} catch (loadError) {
if (isMounted) {
@@ -92,6 +155,16 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
};
}, [backendPreference]);
useEffect(() => {
void loadCompanies(backendPreference);
}, [backendPreference, loadCompanies]);
useEffect(() => {
if (selectedCompany && !companyNames.includes(selectedCompany)) {
setSelectedCompany("");
}
}, [companyNames, selectedCompany]);
return (
<>
<PageHeader
@@ -99,26 +172,56 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
description="Weekly scheduler calendar starts on Sunday and ends on Saturday so you can see who is working each day."
/>
{error ? <p className={styles.error}>{error}</p> : null}
{companiesError ? <p className={styles.error}>{companiesError}</p> : null}
{isLoading ? <p className={styles.muted}>Loading schedule...</p> : null}
<div className={styles.scheduleToolbar}>
<div className={styles.weekNavigator} aria-label="Select schedule week">
<button type="button" onClick={showPreviousWeek} aria-label="Previous week">
<FaChevronLeft aria-hidden="true" />
</button>
<h2>
Week from {formatDate(selectedWeekStart)} to {formatDate(selectedWeekEnd)}
</h2>
<button type="button" onClick={showNextWeek} aria-label="Next week">
<FaChevronRight aria-hidden="true" />
</button>
<label className={styles.companyFilter}>
<select
value={selectedCompany}
onChange={(event) => setSelectedCompany(event.target.value)}
>
<option value="">All companies</option>
{companyNames.map((company) => (
<option key={company} value={company}>
{company}
</option>
))}
</select>
</label>
</div>
</div>
<section className={styles.calendar} aria-label="Weekly schedule from Sunday to Saturday">
{weekDays.map((day) => (
{selectedWeekDays.map(({ day, date }) => (
<article className={styles.dayCard} key={day}>
<h3>{formatDay(day)}</h3>
<h3>
{formatDay(day)}
<span>{formatShortDate(date)}</span>
</h3>
<div className={styles.shiftList}>
{schedulesByDay[day].length > 0 ? (
schedulesByDay[day].map((schedule) => {
const user = users.find((item) => item.id === schedule.user_id);
return (
<div className={styles.shift} key={schedule.id}>
<strong>
{user ? `${user.name} ${user.last_name}` : `User #${schedule.user_id}`}
</strong>
<span>
{formatTime(schedule.enter_time)} - {formatTime(schedule.out_time)}
</span>
</div>
);
})
schedulesByDay[day].map((schedule) => (
<div className={styles.shift} key={schedule.id}>
<strong>
{schedule.user
? `${schedule.user.name} ${schedule.user.last_name}`
: "Unknown user"}
</strong>
<span>{schedule.company ?? "No company"}</span>
<span>
{formatTime(schedule.enter_time)} - {formatTime(schedule.out_time)}
</span>
</div>
))
) : (
<p className={styles.muted}>No shifts scheduled</p>
)}