223 lines
9.5 KiB
TypeScript
223 lines
9.5 KiB
TypeScript
import { JSX, useEffect, useState } from "react";
|
|
import styles from "./Calendar.module.css";
|
|
import { getMonthName, daysInMonth } from "../utils/dateUtils";
|
|
import { customerState } from "../store";
|
|
import { Box } from "../types";
|
|
import { useAtom } from "jotai";
|
|
import { markBoxDelivered, markBoxPickedUp } from "../api";
|
|
import { Modal } from "@mui/material";
|
|
|
|
export default function Calendar() {
|
|
const boxStatuses = ["no boxes", "picked up", "delivered"]; //TODO: There's logic problem between "delivered" and "picked up"
|
|
const [customerData, setCustomerData] = useAtom(customerState);
|
|
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
|
|
const [calendarCells, setCalendarCells] = useState<JSX.Element[]>([]);
|
|
const [firstWeekPadding, setFirstWeekPadding] = useState<JSX.Element[]>([]);
|
|
const [showLatestBoxModal, setShowLatestBoxModal] = useState(false);
|
|
const [latestBoxStatus, setLatestBoxStatus] = useState<string>(boxStatuses[0]);
|
|
|
|
const generateFirstWeekPadding = (month: number) => {
|
|
const firstDay = new Date(new Date().getFullYear(), month, 1).getDay();
|
|
const padding = Array.from({ length: firstDay === 0 ? 6 : firstDay - 1 }, (_, index) => (
|
|
<div key={index}>
|
|
|
|
</div>
|
|
));
|
|
return padding;
|
|
}
|
|
|
|
const generateCalendarCells = (month: number, boxes: Box[]) => {
|
|
const monthDays = daysInMonth[month];
|
|
|
|
const calendarCells = Array.from({ length: monthDays }, (_, index) => {
|
|
const day = index + 1;
|
|
const date = new Date(new Date().getFullYear(), month, day);
|
|
const matchingBox = boxes.find(box => {
|
|
const boxDate = new Date(box.delivery_date);
|
|
return boxDate.getDate() === day && boxDate.getMonth() === month && boxDate.getFullYear() === date.getFullYear();
|
|
});
|
|
|
|
if (matchingBox) {
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={`${styles.calendarCell} ${matchingBox.pickup_date ? styles.pickedUpCell : styles.deliveredCell}`}
|
|
onClick={() => {
|
|
if (matchingBox.pickup_date) {
|
|
// If the box is already picked up, mark it as delivered
|
|
markBoxDelivered(matchingBox.id).then((updatedBox) => {
|
|
setCustomerData((prev) => ({
|
|
...prev,
|
|
boxes: prev.boxes.map(box =>
|
|
box.id === updatedBox.id ? { ...box, pickup_date: null } : box
|
|
),
|
|
}));
|
|
}
|
|
).catch((error) => {
|
|
console.error("Error marking box as delivered:", error);
|
|
});
|
|
return;
|
|
} else {
|
|
markBoxPickedUp(matchingBox.id).then((updatedBox) => {
|
|
setCustomerData((prev) => ({
|
|
...prev,
|
|
boxes: prev.boxes.map(box =>
|
|
box.id === updatedBox.id ? { ...box, pickup_date: updatedBox.pickup_date } : box
|
|
),
|
|
}));
|
|
}
|
|
).catch((error) => {
|
|
console.error("Error marking box as picked up:", error);
|
|
})
|
|
}
|
|
}}>
|
|
{date.toLocaleDateString("fi-FI", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
})}
|
|
<br />
|
|
{matchingBox.pickup_date ? (
|
|
<span className={styles.statusText}>Haettu</span>
|
|
) : (
|
|
<span className={styles.statusText}>Jako</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div key={index} className={`${styles.calendarCell}`}>
|
|
{date.toLocaleDateString("fi-FI", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
})}
|
|
</div>
|
|
);
|
|
});
|
|
return calendarCells;
|
|
}
|
|
|
|
const renderLatestBoxButton = () => {
|
|
switch (latestBoxStatus) {
|
|
case "no boxes":
|
|
return <button
|
|
className={styles.latestBoxNoBoxesBtn}
|
|
disabled
|
|
>
|
|
Ei laatikoita tässä kuussa
|
|
</button>;
|
|
case "picked up":
|
|
return <button
|
|
className={styles.latestBoxPickedUpBtn}
|
|
disabled
|
|
>
|
|
Uusin laatikko on jo haettu
|
|
</button>;
|
|
case "delivered":
|
|
return <button
|
|
className={styles.latestBoxDeliveredBtn}
|
|
onClick={handleLatestBoxClick}
|
|
>
|
|
Kuittaa viimeisin laatikko haetuksi
|
|
</button>;
|
|
default:
|
|
return <span className={styles.latestBoxStatus}>Virhe</span>;
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
setCalendarCells(generateCalendarCells(selectedMonth, customerData.boxes));
|
|
setFirstWeekPadding(generateFirstWeekPadding(selectedMonth));
|
|
setLatestBoxStatus(() => {
|
|
if (!customerData.boxes || customerData.boxes.length === 0) {
|
|
return boxStatuses[0]; // no boxes
|
|
}
|
|
const monthBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth);
|
|
const deliveredBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth && !box.pickup_date);
|
|
if (monthBoxes.length === 0) {
|
|
return boxStatuses[0]; // no boxes
|
|
}
|
|
if (!deliveredBoxes || deliveredBoxes.length === 0) {
|
|
return boxStatuses[1]; // all boxes picked up
|
|
}
|
|
return boxStatuses[2]; // delivered
|
|
}
|
|
);
|
|
}, [selectedMonth, customerData.boxes]);
|
|
|
|
const handleLatestBoxClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
event.preventDefault();
|
|
setShowLatestBoxModal(true);
|
|
}
|
|
|
|
const markLatestBoxPickedUp = () => {
|
|
const deliveredBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth && !box.pickup_date);
|
|
if (deliveredBoxes.length === 0) return;
|
|
const latestBox = deliveredBoxes[0];
|
|
markBoxPickedUp(latestBox.id).then((updatedBox) => {
|
|
setCustomerData((prev) => ({
|
|
...prev,
|
|
boxes: prev.boxes.map(box =>
|
|
box.id === updatedBox.id ? { ...box, pickup_date: updatedBox.pickup_date } : box
|
|
),
|
|
}));
|
|
}).catch((error) => {
|
|
console.error("Error marking latest box as picked up:", error);
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<h2 className={styles.monthTitle}>
|
|
{getMonthName(selectedMonth)} {new Date().getFullYear()}
|
|
</h2>
|
|
<div className={styles.monthNavigation}>
|
|
<button onClick={() => setSelectedMonth((prev) => (prev - 1 + 12) % 12)} className={styles.monthNavBtn}>
|
|
< Edellinen kuukausi
|
|
</button>
|
|
<button onClick={() => setSelectedMonth((prev) => (prev + 1) % 12)} className={styles.monthNavBtn}>
|
|
Seuraava kuukausi >
|
|
</button>
|
|
</div>
|
|
<div className={styles.latestBoxBtnContainer}>
|
|
{renderLatestBoxButton()}
|
|
</div>
|
|
<div className={styles.calendarGrid}>
|
|
<span>Ma</span>
|
|
<span>Ti</span>
|
|
<span>Ke</span>
|
|
<span>To</span>
|
|
<span>Pe</span>
|
|
<span>La</span>
|
|
<span>Su</span>
|
|
{firstWeekPadding}
|
|
{calendarCells}
|
|
</div>
|
|
<Modal
|
|
open={showLatestBoxModal}
|
|
onClose={() => setShowLatestBoxModal(false)}
|
|
className={styles.latestBoxModal}
|
|
>
|
|
<div className={styles.latestBoxModalContent}>
|
|
<h2>Viimeisin laatikko</h2>
|
|
<p>Oletko varma, että haluat kuitata viimeisimmän laatikon haetuksi?</p>
|
|
<button
|
|
className={styles.latestBoxConfirmButton}
|
|
onClick={() => {
|
|
markLatestBoxPickedUp();
|
|
setShowLatestBoxModal(false);
|
|
}}
|
|
>
|
|
Kyllä
|
|
</button>
|
|
<button
|
|
className={styles.latestBoxCancelButton}
|
|
onClick={() => setShowLatestBoxModal(false)}
|
|
>
|
|
Peruuta
|
|
</button>
|
|
</div>
|
|
</Modal>
|
|
</>
|
|
);
|
|
} |