Procházet zdrojové kódy

1. Company Holiday month view and list view

2. Update Swal class, to be alwasy on top
tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi před 1 rokem
rodič
revize
65f6987b8e
8 změnil soubory, kde provedl 166 přidání a 26 odebrání
  1. +33
    -0
      src/app/api/holidays/actions.ts
  2. +10
    -3
      src/app/api/holidays/index.ts
  3. +6
    -0
      src/app/utils/formatUtil.ts
  4. +74
    -14
      src/components/CompanyHoliday/CompanyHoliday.tsx
  5. +15
    -7
      src/components/CompanyHoliday/CompanyHolidayDialog.tsx
  6. +12
    -2
      src/components/CompanyHoliday/CompanyHolidayWrapper.tsx
  7. +9
    -0
      src/components/Swal/CustomAlerts.js
  8. +7
    -0
      src/components/Swal/sweetalert2.css

+ 33
- 0
src/app/api/holidays/actions.ts Zobrazit soubor

@@ -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
};


+ 10
- 3
src/app/api/holidays/index.ts Zobrazit soubor

@@ -4,7 +4,8 @@ import { cache } from "react";
import "server-only"; import "server-only";
import EventInput from '@fullcalendar/react'; import EventInput from '@fullcalendar/react';


export interface HolidaysResult extends EventInput {
export interface HolidaysList extends EventInput {
id: string;
title: string; title: string;
date: string; date: string;
extendedProps: { extendedProps: {
@@ -12,12 +13,18 @@ export interface HolidaysResult extends EventInput {
}; };
} }


export interface HolidaysResult {
id: string;
name: string;
date: number[];
}

export const preloadCompanys = () => { export const preloadCompanys = () => {
fetchHolidays(); fetchHolidays();
}; };


export const fetchHolidays = cache(async () => { export const fetchHolidays = cache(async () => {
return serverFetchJson<HolidaysResult[]>(`${BASE_API_URL}/companys`, {
next: { tags: ["companys"] },
return serverFetchJson<HolidaysResult[]>(`${BASE_API_URL}/company-holidays`, {
next: { tags: ["company-holidays"] },
}); });
}); });

+ 6
- 0
src/app/utils/formatUtil.ts Zobrazit soubor

@@ -30,6 +30,12 @@ export const convertDateArrayToString = (dateArray: number[], format: string = O
return dayjs(dateString).format(format) 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", { const shortDateFormatter_en = new Intl.DateTimeFormat("en-HK", {


+ 74
- 14
src/components/CompanyHoliday/CompanyHoliday.tsx Zobrazit soubor

@@ -1,32 +1,46 @@
"use client"; "use client";


import { HolidaysResult } from "@/app/api/holidays";
import { HolidaysList, HolidaysResult } from "@/app/api/holidays";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, Stack } from '@mui/material/'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, Stack } from '@mui/material/';
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import FullCalendar from '@fullcalendar/react' import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' // a plugin! import dayGridPlugin from '@fullcalendar/daygrid' // a plugin!
import interactionPlugin from "@fullcalendar/interaction" // needed for dayClick import interactionPlugin from "@fullcalendar/interaction" // needed for dayClick
import listPlugin from '@fullcalendar/list';
import Holidays from "date-holidays"; import Holidays from "date-holidays";
import CompanyHolidayDialog from "./CompanyHolidayDialog"; 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 { 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 { interface Props {
holidays: HolidaysResult[];
holidays: HolidaysList[];
} }


const CompanyHoliday: React.FC<Props> = ({ holidays }) => { const CompanyHoliday: React.FC<Props> = ({ holidays }) => {
const { t } = useTranslation("holidays"); const { t } = useTranslation("holidays");
const router = useRouter();
const formValues = useFormContext();
const [serverError, setServerError] = useState("");


const hd = new Holidays('HK') const hd = new Holidays('HK')
console.log(holidays) console.log(holidays)


const [companyHolidays, setCompanyHolidays] = useState<HolidaysResult[]>([])
const [companyHolidays, setCompanyHolidays] = useState<HolidaysList[]>([])
const [dateContent, setDateContent] = useState<{ date: string }>({date: ''}) const [dateContent, setDateContent] = useState<{ date: string }>({date: ''})
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [isEdit, setIsEdit] = useState(false);
const [editable, setEditable] = useState(true);


const handleClose = () => { const handleClose = () => {
setOpen(false); setOpen(false);
setEditable(true)
setIsEdit(false)
formProps.setValue("name", "")
formProps.setValue("id", null)
}; };


const getPublicHolidaysList = () => { const getPublicHolidaysList = () => {
@@ -92,34 +106,69 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => {
return {date: `${tempYear}-${tempMonth}-${tempDate}`, title: tempName, extendedProps: {calendar: 'holiday'}} 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(()=>{ useEffect(()=>{
getPublicHolidaysList() getPublicHolidaysList()
},[]) },[])


useEffect(()=>{

},[holidays])

const handleDateClick = (event:any) => { const handleDateClick = (event:any) => {
console.log(event.dateStr)
// console.log(event.dateStr)
setDateContent({date: event.dateStr}) setDateContent({date: event.dateStr})
setOpen(true); setOpen(true);
} }
const handleEventClick = (event:any) => { 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<SubmitHandler<any>>( const onSubmit = useCallback<SubmitHandler<any>>(
async (data) => { async (data) => {
try { 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) { } catch (e) {
console.log(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<SubmitErrorHandler<any>>( const onSubmitError = useCallback<SubmitErrorHandler<any>>(
(errors) => { (errors) => {
@@ -131,7 +180,8 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => {


const formProps = useForm<any>({ const formProps = useForm<any>({
defaultValues: { defaultValues: {
title: ""
id: null,
name: ""
}, },
}); });


@@ -139,24 +189,34 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => {
<> <>
<FormProvider {...formProps}> <FormProvider {...formProps}>
<FullCalendar <FullCalendar
plugins={[ dayGridPlugin, interactionPlugin ]}
plugins={[ dayGridPlugin, interactionPlugin, listPlugin ]}
initialView="dayGridMonth" initialView="dayGridMonth"
events={companyHolidays} events={companyHolidays}
eventColor='#ff0000' eventColor='#ff0000'
dateClick={handleDateClick} dateClick={handleDateClick}
eventClick={handleEventClick} eventClick={handleEventClick}
headerToolbar={{
start: "today prev next",
end: "dayGridMonth listMonth"
}}
buttonText={{
month: t("Month view"),
list: t("List View")
}}
/> />
<CompanyHolidayDialog <CompanyHolidayDialog
open={open} open={open}
onClose={handleClose} onClose={handleClose}
title={("Create Holiday")}
title={!editable ? "Bank Holiday" : isEdit ? "Edit Holiday" : "Create Holiday"}
content={dateContent} content={dateContent}
actions={ actions={
<Stack direction="row" justifyContent="flex-end" gap={1} component="form" onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}> <Stack direction="row" justifyContent="flex-end" gap={1} component="form" onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}>
<Button onClick={handleClose}>Close</Button> <Button onClick={handleClose}>Close</Button>
<Button type="submit">Submit</Button>
{isEdit && <Button disabled={!editable} onClick={handleDelete}>Delete</Button>}
<Button disabled={!editable} type="submit">Submit</Button>
</Stack> </Stack>
} }
editable={editable}
/> />
</FormProvider> </FormProvider>
</> </>


+ 15
- 7
src/components/CompanyHoliday/CompanyHolidayDialog.tsx Zobrazit soubor

@@ -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 { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Grid, FormControl } from '@mui/material/';
import { useForm, useFormContext } from 'react-hook-form'; import { useForm, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -12,14 +12,15 @@ interface CompanyHolidayDialogProps {
onClose: () => void; onClose: () => void;
title: string; title: string;
actions: React.ReactNode; actions: React.ReactNode;
content: Content
content: Content;
editable: Boolean;
} }


interface Content { interface Content {
date: string date: string
} }


const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClose, title, actions, content }) => {
const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClose, title, actions, content, editable }) => {
const { const {
t, t,
i18n: { language }, i18n: { language },
@@ -29,9 +30,14 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo
register, register,
formState: { errors }, formState: { errors },
setValue, setValue,
getValues,
} = useFormContext<any>(); } = useFormContext<any>();


useEffect(() => {
setValue("date", content.date);
}, [content])

console.log(editable)

return ( return (
<LocalizationProvider <LocalizationProvider
dateAdapter={AdapterDayjs} dateAdapter={AdapterDayjs}
@@ -43,22 +49,24 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={12}> <Grid item xs={12}>
<TextField <TextField
disabled={!editable}
label={t("title")} label={t("title")}
fullWidth fullWidth
{...register("title", {
{...register("name", {
required: "title required!", required: "title required!",
})} })}
error={Boolean(errors.title)}
error={Boolean(errors.name)}
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<FormControl fullWidth> <FormControl fullWidth>
<DatePicker <DatePicker
disabled={!editable}
label={t("Company Holiday")} label={t("Company Holiday")}
value={dayjs(content.date)} value={dayjs(content.date)}
onChange={(date) => { onChange={(date) => {
if (!date) return; if (!date) return;
setValue("dueDate", date.format(INPUT_DATE_FORMAT));
setValue("date", date.format(INPUT_DATE_FORMAT));
}} }}
slotProps={{ slotProps={{
textField: { textField: {


+ 12
- 2
src/components/CompanyHoliday/CompanyHolidayWrapper.tsx Zobrazit soubor

@@ -4,7 +4,8 @@ import CompanyHoliday from "./CompanyHoliday";
import CompanyHolidayLoading from "./CompanyHolidayLoading"; import CompanyHolidayLoading from "./CompanyHolidayLoading";
import { fetchCompanys } from "@/app/api/companys"; import { fetchCompanys } from "@/app/api/companys";
import Holidays from "date-holidays"; 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 { interface SubComponents {
Loading: typeof CompanyHolidayLoading; Loading: typeof CompanyHolidayLoading;
@@ -14,9 +15,18 @@ const CompanyHolidayWrapper: React.FC & SubComponents = async () => {
// const Companys = await fetchCompanys(); // const Companys = await fetchCompanys();


const companyHolidays: HolidaysResult[] = await fetchHolidays() 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 <CompanyHoliday holidays={companyHolidays} />;
return <CompanyHoliday holidays={convertedHolidays as HolidaysList[]} />;
}; };


CompanyHolidayWrapper.Loading = CompanyHolidayLoading; CompanyHolidayWrapper.Loading = CompanyHolidayLoading;


+ 9
- 0
src/components/Swal/CustomAlerts.js Zobrazit soubor

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import "./sweetalert2.css"


export const msg = (text) => { export const msg = (text) => {
Swal.mixin({ Swal.mixin({
@@ -59,6 +60,10 @@ export const submitDialog = async (confirmAction, t, {...props}) => {
confirmButtonText: props.confirmButtonText ?? t("Submit"), confirmButtonText: props.confirmButtonText ?? t("Submit"),
showCancelButton: true, showCancelButton: true,
showConfirmButton: 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) { if (result.isConfirmed) {
confirmAction(); confirmAction();
@@ -74,6 +79,10 @@ export const deleteDialog = async (confirmAction, t) => {
confirmButtonText: t("Delete"), confirmButtonText: t("Delete"),
showCancelButton: true, showCancelButton: true,
showConfirmButton: 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) { if (result.isConfirmed) {
confirmAction(); confirmAction();


+ 7
- 0
src/components/Swal/sweetalert2.css Zobrazit soubor

@@ -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 */
}

Načítá se…
Zrušit
Uložit