adding schedule by company
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"react": "^19.3.0",
|
"react": "^19.3.0",
|
||||||
"react-dom": "^19.3.0",
|
"react-dom": "^19.3.0",
|
||||||
|
"react-icons": "^5.7.0",
|
||||||
"react-router": "^8.4.0",
|
"react-router": "^8.4.0",
|
||||||
"sass": "^1.104.1",
|
"sass": "^1.104.1",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
|
|||||||
Generated
+990
-1806
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import {
|
|||||||
assignEmployeeToCompany,
|
assignEmployeeToCompany,
|
||||||
createCompany,
|
createCompany,
|
||||||
deleteCompany,
|
deleteCompany,
|
||||||
getCompanies,
|
|
||||||
getCompanyEmployeeNames,
|
getCompanyEmployeeNames,
|
||||||
} from "@api/companiesApi";
|
} from "@api/companiesApi";
|
||||||
import { getUsers } from "@api/usersApi";
|
import { getUsers } from "@api/usersApi";
|
||||||
@@ -13,6 +12,7 @@ import { PageHeader } from "@components/PageHeader";
|
|||||||
import type { BackendPreference } from "@/types/backend";
|
import type { BackendPreference } from "@/types/backend";
|
||||||
import type { Company } from "@/types/companies";
|
import type { Company } from "@/types/companies";
|
||||||
import type { Employee } from "@/types/users";
|
import type { Employee } from "@/types/users";
|
||||||
|
import { useCompaniesStore } from "../store/companiesStore";
|
||||||
import styles from "./CompaniesPage.module.scss";
|
import styles from "./CompaniesPage.module.scss";
|
||||||
|
|
||||||
type CompaniesPageProps = {
|
type CompaniesPageProps = {
|
||||||
@@ -22,7 +22,6 @@ type CompaniesPageProps = {
|
|||||||
type EmployeeNamesByCompany = Record<number, string[]>;
|
type EmployeeNamesByCompany = Record<number, string[]>;
|
||||||
|
|
||||||
export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
||||||
const [companies, setCompanies] = useState<Company[]>([]);
|
|
||||||
const [employeeNamesByCompany, setEmployeeNamesByCompany] = useState<EmployeeNamesByCompany>({});
|
const [employeeNamesByCompany, setEmployeeNamesByCompany] = useState<EmployeeNamesByCompany>({});
|
||||||
const [users, setUsers] = useState<Employee[]>([]);
|
const [users, setUsers] = useState<Employee[]>([]);
|
||||||
const [companyName, setCompanyName] = useState("");
|
const [companyName, setCompanyName] = useState("");
|
||||||
@@ -34,6 +33,8 @@ export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
|||||||
const [deletingCompanyId, setDeletingCompanyId] = useState<number | null>(null);
|
const [deletingCompanyId, setDeletingCompanyId] = useState<number | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const companies = useCompaniesStore((state) => state.companies);
|
||||||
|
const refreshCompanies = useCompaniesStore((state) => state.loadCompanies);
|
||||||
|
|
||||||
const loadCompanies = useCallback(async () => {
|
const loadCompanies = useCallback(async () => {
|
||||||
setError("");
|
setError("");
|
||||||
@@ -41,7 +42,7 @@ export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const [companiesResponse, usersResponse] = await Promise.all([
|
const [companiesResponse, usersResponse] = await Promise.all([
|
||||||
getCompanies(backendPreference),
|
refreshCompanies(backendPreference),
|
||||||
getUsers(backendPreference),
|
getUsers(backendPreference),
|
||||||
]);
|
]);
|
||||||
const employeeNameEntries = await Promise.all(
|
const employeeNameEntries = await Promise.all(
|
||||||
@@ -51,7 +52,6 @@ export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
setCompanies(companiesResponse);
|
|
||||||
setUsers(usersResponse);
|
setUsers(usersResponse);
|
||||||
setEmployeeNamesByCompany(Object.fromEntries(employeeNameEntries));
|
setEmployeeNamesByCompany(Object.fromEntries(employeeNameEntries));
|
||||||
} catch (loadError) {
|
} catch (loadError) {
|
||||||
@@ -59,7 +59,7 @@ export function CompaniesPage({ backendPreference }: CompaniesPageProps) {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [backendPreference]);
|
}, [backendPreference, refreshCompanies]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadCompanies();
|
void loadCompanies();
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ export function HomePage({ backendPreference }: HomePageProps) {
|
|||||||
|
|
||||||
const workingToday = new Set(
|
const workingToday = new Set(
|
||||||
schedules
|
schedules
|
||||||
.filter((schedule) => schedule.enter_time && !schedule.out_time)
|
.filter(
|
||||||
|
(schedule): schedule is Schedule & { user_id: number } =>
|
||||||
|
typeof schedule.user_id === "number" && Boolean(schedule.enter_time && !schedule.out_time),
|
||||||
|
)
|
||||||
.map((schedule) => schedule.user_id),
|
.map((schedule) => schedule.user_id),
|
||||||
).size;
|
).size;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,66 @@
|
|||||||
|
.scheduleToolbar {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekNavigator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
min-width: min(520px, 100%);
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #1f2937;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 14px 35px rgba(17, 24, 39, 0.07);
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 0.18s ease,
|
||||||
|
box-shadow 0.18s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 18px 38px rgba(17, 24, 39, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.companyFilter {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
justify-self: center;
|
||||||
|
min-width: min(320px, 100%);
|
||||||
|
color: #687487;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 11px 14px;
|
||||||
|
color: #1f2937;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 14px 35px rgba(17, 24, 39, 0.07);
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.calendar {
|
.calendar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, minmax(130px, 1fr));
|
grid-template-columns: repeat(7, minmax(130px, 1fr));
|
||||||
@@ -14,6 +77,14 @@
|
|||||||
h3 {
|
h3 {
|
||||||
margin: 0 0 14px;
|
margin: 0 0 14px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #687487;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +121,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
|
.scheduleToolbar {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekNavigator {
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.calendar {
|
.calendar {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
+128
-25
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||||
import { getWeekSchedule } from "@api/scheduleApi";
|
import { getWeekSchedule } from "@api/scheduleApi";
|
||||||
import { getUsers } from "@api/usersApi";
|
|
||||||
import { PageHeader } from "@components/PageHeader";
|
import { PageHeader } from "@components/PageHeader";
|
||||||
import type { BackendPreference } from "@/types/backend";
|
import type { BackendPreference } from "@/types/backend";
|
||||||
import type { Schedule } from "@/types/schedule";
|
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";
|
import styles from "./SchedulePage.module.scss";
|
||||||
|
|
||||||
type SchedulePageProps = {
|
type SchedulePageProps = {
|
||||||
@@ -25,6 +26,31 @@ function formatDay(day: DayOfWeek): string {
|
|||||||
return day.charAt(0).toUpperCase() + day.slice(1);
|
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 {
|
function formatTime(value: string | null): string {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "Not registered";
|
return "Not registered";
|
||||||
@@ -35,14 +61,47 @@ function formatTime(value: string | null): string {
|
|||||||
|
|
||||||
export function SchedulePage({ backendPreference }: SchedulePageProps) {
|
export function SchedulePage({ backendPreference }: SchedulePageProps) {
|
||||||
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
||||||
const [users, setUsers] = useState<Employee[]>([]);
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
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(() => {
|
const schedulesByDay = useMemo(() => {
|
||||||
return schedules.reduce<Record<DayOfWeek, Schedule[]>>(
|
return schedules.reduce<Record<DayOfWeek, Schedule[]>>(
|
||||||
(accumulator, 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;
|
return accumulator;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,7 +114,15 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
|
|||||||
saturday: [],
|
saturday: [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, [schedules]);
|
}, [schedules, selectedCompany, selectedWeekEndExclusive, selectedWeekStart]);
|
||||||
|
|
||||||
|
function showPreviousWeek() {
|
||||||
|
setSelectedWeekStart((currentWeekStart) => addDays(currentWeekStart, -7));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNextWeek() {
|
||||||
|
setSelectedWeekStart((currentWeekStart) => addDays(currentWeekStart, 7));
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -65,14 +132,10 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [scheduleResponse, usersResponse] = await Promise.all([
|
const scheduleResponse = await getWeekSchedule(backendPreference);
|
||||||
getWeekSchedule(backendPreference),
|
|
||||||
getUsers(backendPreference),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setSchedules(scheduleResponse.schedules);
|
setSchedules(scheduleResponse.schedules);
|
||||||
setUsers(usersResponse);
|
|
||||||
}
|
}
|
||||||
} catch (loadError) {
|
} catch (loadError) {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
@@ -92,6 +155,16 @@ export function SchedulePage({ backendPreference }: SchedulePageProps) {
|
|||||||
};
|
};
|
||||||
}, [backendPreference]);
|
}, [backendPreference]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadCompanies(backendPreference);
|
||||||
|
}, [backendPreference, loadCompanies]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedCompany && !companyNames.includes(selectedCompany)) {
|
||||||
|
setSelectedCompany("");
|
||||||
|
}
|
||||||
|
}, [companyNames, selectedCompany]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<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."
|
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}
|
{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}
|
{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">
|
<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}>
|
<article className={styles.dayCard} key={day}>
|
||||||
<h3>{formatDay(day)}</h3>
|
<h3>
|
||||||
|
{formatDay(day)}
|
||||||
|
<span>{formatShortDate(date)}</span>
|
||||||
|
</h3>
|
||||||
<div className={styles.shiftList}>
|
<div className={styles.shiftList}>
|
||||||
{schedulesByDay[day].length > 0 ? (
|
{schedulesByDay[day].length > 0 ? (
|
||||||
schedulesByDay[day].map((schedule) => {
|
schedulesByDay[day].map((schedule) => (
|
||||||
const user = users.find((item) => item.id === schedule.user_id);
|
<div className={styles.shift} key={schedule.id}>
|
||||||
return (
|
<strong>
|
||||||
<div className={styles.shift} key={schedule.id}>
|
{schedule.user
|
||||||
<strong>
|
? `${schedule.user.name} ${schedule.user.last_name}`
|
||||||
{user ? `${user.name} ${user.last_name}` : `User #${schedule.user_id}`}
|
: "Unknown user"}
|
||||||
</strong>
|
</strong>
|
||||||
<span>
|
<span>{schedule.company ?? "No company"}</span>
|
||||||
{formatTime(schedule.enter_time)} - {formatTime(schedule.out_time)}
|
<span>
|
||||||
</span>
|
{formatTime(schedule.enter_time)} - {formatTime(schedule.out_time)}
|
||||||
</div>
|
</span>
|
||||||
);
|
</div>
|
||||||
})
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className={styles.muted}>No shifts scheduled</p>
|
<p className={styles.muted}>No shifts scheduled</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ export function UsersPage({ backendPreference }: UsersPageProps) {
|
|||||||
|
|
||||||
const schedulesByUser = useMemo(() => {
|
const schedulesByUser = useMemo(() => {
|
||||||
return schedules.reduce<Record<number, Schedule[]>>((accumulator, schedule) => {
|
return schedules.reduce<Record<number, Schedule[]>>((accumulator, schedule) => {
|
||||||
|
if (typeof schedule.user_id !== "number") {
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
accumulator[schedule.user_id] = [...(accumulator[schedule.user_id] ?? []), schedule];
|
accumulator[schedule.user_id] = [...(accumulator[schedule.user_id] ?? []), schedule];
|
||||||
return accumulator;
|
return accumulator;
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { getCompanies } from "@api/companiesApi";
|
||||||
|
import type { BackendPreference } from "@/types/backend";
|
||||||
|
import type { Company } from "@/types/companies";
|
||||||
|
|
||||||
|
type CompaniesState = {
|
||||||
|
companies: Company[];
|
||||||
|
companyNames: string[];
|
||||||
|
error: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
loadCompanies: (backendPreference: BackendPreference) => Promise<Company[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCompaniesStore = create<CompaniesState>((set) => ({
|
||||||
|
companies: [],
|
||||||
|
companyNames: [],
|
||||||
|
error: "",
|
||||||
|
isLoading: false,
|
||||||
|
async loadCompanies(backendPreference) {
|
||||||
|
set({ error: "", isLoading: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const companies = await getCompanies(backendPreference);
|
||||||
|
const companyNames = companies
|
||||||
|
.map((company) => company.name)
|
||||||
|
.sort((firstCompany, secondCompany) => firstCompany.localeCompare(secondCompany));
|
||||||
|
|
||||||
|
set({ companies, companyNames, isLoading: false });
|
||||||
|
return companies;
|
||||||
|
} catch (loadError) {
|
||||||
|
const error = loadError instanceof Error ? loadError.message : "Unable to load companies";
|
||||||
|
set({ error, isLoading: false });
|
||||||
|
throw loadError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
+10
-2
@@ -1,8 +1,16 @@
|
|||||||
import type { DayOfWeek } from "@/types/users";
|
import type { DayOfWeek, WorkTime } from "@/types/users";
|
||||||
|
|
||||||
|
export type ScheduleUser = {
|
||||||
|
name: string;
|
||||||
|
last_name: string;
|
||||||
|
preference: WorkTime;
|
||||||
|
};
|
||||||
|
|
||||||
export type Schedule = {
|
export type Schedule = {
|
||||||
id: number;
|
id: number;
|
||||||
user_id: number;
|
user_id?: number;
|
||||||
|
user?: ScheduleUser;
|
||||||
|
company?: string;
|
||||||
enter_time: string | null;
|
enter_time: string | null;
|
||||||
day_of_week: DayOfWeek;
|
day_of_week: DayOfWeek;
|
||||||
out_time: string | null;
|
out_time: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user