From 65f6987b8eff5014698da76d3d4d2d5f8930eea0 Mon Sep 17 00:00:00 2001 From: "MSI\\2Fi" Date: Mon, 6 May 2024 17:06:13 +0800 Subject: [PATCH] 1. Company Holiday month view and list view 2. Update Swal class, to be alwasy on top --- src/app/api/holidays/actions.ts | 33 +++++++ src/app/api/holidays/index.ts | 13 ++- src/app/utils/formatUtil.ts | 6 ++ .../CompanyHoliday/CompanyHoliday.tsx | 88 ++++++++++++++++--- .../CompanyHoliday/CompanyHolidayDialog.tsx | 22 +++-- .../CompanyHoliday/CompanyHolidayWrapper.tsx | 14 ++- src/components/Swal/CustomAlerts.js | 9 ++ src/components/Swal/sweetalert2.css | 7 ++ 8 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 src/app/api/holidays/actions.ts create mode 100644 src/components/Swal/sweetalert2.css diff --git a/src/app/api/holidays/actions.ts b/src/app/api/holidays/actions.ts new file mode 100644 index 0000000..8874418 --- /dev/null +++ b/src/app/api/holidays/actions.ts @@ -0,0 +1,33 @@ +"use server"; + +import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { Dayjs } from "dayjs"; +import { cache } from "react"; + +export interface CreateCompanyHolidayInputs { + id: number; + name: string; + date: string; +} + +export const saveCompanyHoliday = async (data: CreateCompanyHolidayInputs) => { + return serverFetchJson(`${BASE_API_URL}/company-holidays/new`, { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }); +}; + +export const deleteCompanyHoliday = async (id: number) => { + const holiday = await serverFetchWithNoContent( + `${BASE_API_URL}/company-holidays/${id}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }, + ); + + return holiday +}; + diff --git a/src/app/api/holidays/index.ts b/src/app/api/holidays/index.ts index 0a90ffc..1bf21aa 100644 --- a/src/app/api/holidays/index.ts +++ b/src/app/api/holidays/index.ts @@ -4,7 +4,8 @@ import { cache } from "react"; import "server-only"; import EventInput from '@fullcalendar/react'; -export interface HolidaysResult extends EventInput { +export interface HolidaysList extends EventInput { + id: string; title: string; date: string; extendedProps: { @@ -12,12 +13,18 @@ export interface HolidaysResult extends EventInput { }; } +export interface HolidaysResult { + id: string; + name: string; + date: number[]; +} + export const preloadCompanys = () => { fetchHolidays(); }; export const fetchHolidays = cache(async () => { - return serverFetchJson(`${BASE_API_URL}/companys`, { - next: { tags: ["companys"] }, + return serverFetchJson(`${BASE_API_URL}/company-holidays`, { + next: { tags: ["company-holidays"] }, }); }); \ No newline at end of file diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 918b0ca..899c1cd 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -30,6 +30,12 @@ export const convertDateArrayToString = (dateArray: number[], format: string = O return dayjs(dateString).format(format) } } + if (dateArray.length === 3) { + if (!needTime) { + const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}` + return dayjs(dateString).format(format) + } + } } const shortDateFormatter_en = new Intl.DateTimeFormat("en-HK", { diff --git a/src/components/CompanyHoliday/CompanyHoliday.tsx b/src/components/CompanyHoliday/CompanyHoliday.tsx index bfa5ecb..291a2df 100644 --- a/src/components/CompanyHoliday/CompanyHoliday.tsx +++ b/src/components/CompanyHoliday/CompanyHoliday.tsx @@ -1,32 +1,46 @@ "use client"; -import { HolidaysResult } from "@/app/api/holidays"; +import { HolidaysList, HolidaysResult } from "@/app/api/holidays"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, Stack } from '@mui/material/'; import { useTranslation } from "react-i18next"; import FullCalendar from '@fullcalendar/react' import dayGridPlugin from '@fullcalendar/daygrid' // a plugin! import interactionPlugin from "@fullcalendar/interaction" // needed for dayClick +import listPlugin from '@fullcalendar/list'; import Holidays from "date-holidays"; import CompanyHolidayDialog from "./CompanyHolidayDialog"; -import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; +import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm, useFormContext } from "react-hook-form"; import { EventBusy } from "@mui/icons-material"; +import { deleteCompanyHoliday, saveCompanyHoliday } from "@/app/api/holidays/actions"; +import { useRouter } from "next/navigation"; +import { deleteDialog, submitDialog } from "../Swal/CustomAlerts"; + interface Props { - holidays: HolidaysResult[]; + holidays: HolidaysList[]; } const CompanyHoliday: React.FC = ({ holidays }) => { const { t } = useTranslation("holidays"); + const router = useRouter(); + const formValues = useFormContext(); + const [serverError, setServerError] = useState(""); const hd = new Holidays('HK') console.log(holidays) - const [companyHolidays, setCompanyHolidays] = useState([]) + const [companyHolidays, setCompanyHolidays] = useState([]) const [dateContent, setDateContent] = useState<{ date: string }>({date: ''}) const [open, setOpen] = useState(false); + const [isEdit, setIsEdit] = useState(false); + const [editable, setEditable] = useState(true); const handleClose = () => { setOpen(false); + setEditable(true) + setIsEdit(false) + formProps.setValue("name", "") + formProps.setValue("id", null) }; const getPublicHolidaysList = () => { @@ -92,34 +106,69 @@ const CompanyHoliday: React.FC = ({ holidays }) => { return {date: `${tempYear}-${tempMonth}-${tempDate}`, title: tempName, extendedProps: {calendar: 'holiday'}} }) - setCompanyHolidays([...events_cyhd, ...events_nyhd] as HolidaysResult[]) + setCompanyHolidays([...events_cyhd, ...events_nyhd, ...holidays] as HolidaysList[]) } useEffect(()=>{ getPublicHolidaysList() },[]) + useEffect(()=>{ + + },[holidays]) + const handleDateClick = (event:any) => { - console.log(event.dateStr) + // console.log(event.dateStr) setDateContent({date: event.dateStr}) setOpen(true); } const handleEventClick = (event:any) => { - console.log(event) + // event.event.id: if id !== "", holiday is created by company + console.log(event.event.id) + if (event.event.id === null || event.event.id === ""){ + setEditable(false) + } + formProps.setValue("name", event.event.title) + formProps.setValue("id", event.event.id) + setDateContent({date: event.event.startStr}) + setOpen(true); + setIsEdit(true); } const onSubmit = useCallback>( async (data) => { try { - console.log(data); - // console.log(JSON.stringify(data)); + // console.log(data); + setServerError(""); + submitDialog(async () => { + await saveCompanyHoliday(data) + window.location.reload() + setOpen(false); + setIsEdit(false); + }, t, {}) } catch (e) { console.log(e); + setServerError(t("An error has occurred. Please try again later.")); } }, - [t, ], + [t, router], ); + + const handleDelete = async (event:any) => { + try { + setServerError(""); + deleteDialog(async () => { + await deleteCompanyHoliday(parseInt(formProps.getValues("id"))) + window.location.reload() + setOpen(false); + setIsEdit(false); + }, t); + } catch (e) { + console.log(e); + setServerError(t("An error has occurred. Please try again later.")); + } + } const onSubmitError = useCallback>( (errors) => { @@ -131,7 +180,8 @@ const CompanyHoliday: React.FC = ({ holidays }) => { const formProps = useForm({ defaultValues: { - title: "" + id: null, + name: "" }, }); @@ -139,24 +189,34 @@ const CompanyHoliday: React.FC = ({ holidays }) => { <> - + {isEdit && } + } + editable={editable} /> diff --git a/src/components/CompanyHoliday/CompanyHolidayDialog.tsx b/src/components/CompanyHoliday/CompanyHolidayDialog.tsx index 3b9419f..78b3d67 100644 --- a/src/components/CompanyHoliday/CompanyHolidayDialog.tsx +++ b/src/components/CompanyHoliday/CompanyHolidayDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, FormControl } from '@mui/material/'; import { useForm, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -12,14 +12,15 @@ interface CompanyHolidayDialogProps { onClose: () => void; title: string; actions: React.ReactNode; - content: Content + content: Content; + editable: Boolean; } interface Content { date: string } -const CompanyHolidayDialog: React.FC = ({ open, onClose, title, actions, content }) => { +const CompanyHolidayDialog: React.FC = ({ open, onClose, title, actions, content, editable }) => { const { t, i18n: { language }, @@ -29,9 +30,14 @@ const CompanyHolidayDialog: React.FC = ({ open, onClo register, formState: { errors }, setValue, - getValues, } = useFormContext(); + useEffect(() => { + setValue("date", content.date); + }, [content]) + + console.log(editable) + return ( = ({ open, onClo { if (!date) return; - setValue("dueDate", date.format(INPUT_DATE_FORMAT)); + setValue("date", date.format(INPUT_DATE_FORMAT)); }} slotProps={{ textField: { diff --git a/src/components/CompanyHoliday/CompanyHolidayWrapper.tsx b/src/components/CompanyHoliday/CompanyHolidayWrapper.tsx index 0c21148..23afb2c 100644 --- a/src/components/CompanyHoliday/CompanyHolidayWrapper.tsx +++ b/src/components/CompanyHoliday/CompanyHolidayWrapper.tsx @@ -4,7 +4,8 @@ import CompanyHoliday from "./CompanyHoliday"; import CompanyHolidayLoading from "./CompanyHolidayLoading"; import { fetchCompanys } from "@/app/api/companys"; import Holidays from "date-holidays"; -import { HolidaysResult, fetchHolidays } from "@/app/api/holidays"; +import { HolidaysResult, fetchHolidays, HolidaysList } from "@/app/api/holidays"; +import { convertDateArrayToString } from "@/app/utils/formatUtil"; interface SubComponents { Loading: typeof CompanyHolidayLoading; @@ -14,9 +15,18 @@ const CompanyHolidayWrapper: React.FC & SubComponents = async () => { // const Companys = await fetchCompanys(); const companyHolidays: HolidaysResult[] = await fetchHolidays() + +// console.log(companyHolidays) + const convertedHolidays = companyHolidays.map((holiday) => { + return { + id: holiday.id.toString(), + title: holiday.name, + date: convertDateArrayToString(holiday.date, "YYYY-MM-DD", false) + } + }) - return ; + return ; }; CompanyHolidayWrapper.Loading = CompanyHolidayLoading; diff --git a/src/components/Swal/CustomAlerts.js b/src/components/Swal/CustomAlerts.js index 668502c..ad1aec6 100644 --- a/src/components/Swal/CustomAlerts.js +++ b/src/components/Swal/CustomAlerts.js @@ -1,6 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import Swal from "sweetalert2"; +import "./sweetalert2.css" export const msg = (text) => { Swal.mixin({ @@ -59,6 +60,10 @@ export const submitDialog = async (confirmAction, t, {...props}) => { confirmButtonText: props.confirmButtonText ?? t("Submit"), showCancelButton: true, showConfirmButton: true, + customClass: { + container: "swal-container-class", // Add a custom class to the Swal.fire container element + popup: "swal-popup-class", // Add a custom class to the Swal.fire popup element + }, }); if (result.isConfirmed) { confirmAction(); @@ -74,6 +79,10 @@ export const deleteDialog = async (confirmAction, t) => { confirmButtonText: t("Delete"), showCancelButton: true, showConfirmButton: true, + customClass: { + container: "swal-container-class", // Add a custom class to the Swal.fire container element + popup: "swal-popup-class", // Add a custom class to the Swal.fire popup element + }, }); if (result.isConfirmed) { confirmAction(); diff --git a/src/components/Swal/sweetalert2.css b/src/components/Swal/sweetalert2.css new file mode 100644 index 0000000..4825796 --- /dev/null +++ b/src/components/Swal/sweetalert2.css @@ -0,0 +1,7 @@ +.swal-container-class { + z-index: 9999; /* Adjust the z-index value as needed */ + } + +.swal-popup-class { +z-index: 10000; /* Adjust the z-index value as needed */ +} \ No newline at end of file