2. Update Swal class, to be alwasy on toptags/Baseline_30082024_FRONTEND_UAT
@@ -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 | |||||
}; | |||||
@@ -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"] }, | |||||
}); | }); | ||||
}); | }); |
@@ -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", { | ||||
@@ -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> | ||||
</> | </> | ||||
@@ -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: { | ||||
@@ -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; | ||||
@@ -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(); | ||||
@@ -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 */ | |||||
} |