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 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<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) | |||
} | |||
} | |||
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", { | |||
@@ -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<Props> = ({ 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<HolidaysResult[]>([]) | |||
const [companyHolidays, setCompanyHolidays] = useState<HolidaysList[]>([]) | |||
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<Props> = ({ 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<SubmitHandler<any>>( | |||
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<SubmitErrorHandler<any>>( | |||
(errors) => { | |||
@@ -131,7 +180,8 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||
const formProps = useForm<any>({ | |||
defaultValues: { | |||
title: "" | |||
id: null, | |||
name: "" | |||
}, | |||
}); | |||
@@ -139,24 +189,34 @@ const CompanyHoliday: React.FC<Props> = ({ holidays }) => { | |||
<> | |||
<FormProvider {...formProps}> | |||
<FullCalendar | |||
plugins={[ dayGridPlugin, interactionPlugin ]} | |||
plugins={[ dayGridPlugin, interactionPlugin, listPlugin ]} | |||
initialView="dayGridMonth" | |||
events={companyHolidays} | |||
eventColor='#ff0000' | |||
dateClick={handleDateClick} | |||
eventClick={handleEventClick} | |||
headerToolbar={{ | |||
start: "today prev next", | |||
end: "dayGridMonth listMonth" | |||
}} | |||
buttonText={{ | |||
month: t("Month view"), | |||
list: t("List View") | |||
}} | |||
/> | |||
<CompanyHolidayDialog | |||
open={open} | |||
onClose={handleClose} | |||
title={("Create Holiday")} | |||
title={!editable ? "Bank Holiday" : isEdit ? "Edit Holiday" : "Create Holiday"} | |||
content={dateContent} | |||
actions={ | |||
<Stack direction="row" justifyContent="flex-end" gap={1} component="form" onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}> | |||
<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> | |||
} | |||
editable={editable} | |||
/> | |||
</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 { 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<CompanyHolidayDialogProps> = ({ open, onClose, title, actions, content }) => { | |||
const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClose, title, actions, content, editable }) => { | |||
const { | |||
t, | |||
i18n: { language }, | |||
@@ -29,9 +30,14 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo | |||
register, | |||
formState: { errors }, | |||
setValue, | |||
getValues, | |||
} = useFormContext<any>(); | |||
useEffect(() => { | |||
setValue("date", content.date); | |||
}, [content]) | |||
console.log(editable) | |||
return ( | |||
<LocalizationProvider | |||
dateAdapter={AdapterDayjs} | |||
@@ -43,22 +49,24 @@ const CompanyHolidayDialog: React.FC<CompanyHolidayDialogProps> = ({ open, onClo | |||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
<Grid item xs={12}> | |||
<TextField | |||
disabled={!editable} | |||
label={t("title")} | |||
fullWidth | |||
{...register("title", { | |||
{...register("name", { | |||
required: "title required!", | |||
})} | |||
error={Boolean(errors.title)} | |||
error={Boolean(errors.name)} | |||
/> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<FormControl fullWidth> | |||
<DatePicker | |||
disabled={!editable} | |||
label={t("Company Holiday")} | |||
value={dayjs(content.date)} | |||
onChange={(date) => { | |||
if (!date) return; | |||
setValue("dueDate", date.format(INPUT_DATE_FORMAT)); | |||
setValue("date", date.format(INPUT_DATE_FORMAT)); | |||
}} | |||
slotProps={{ | |||
textField: { | |||
@@ -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 <CompanyHoliday holidays={companyHolidays} />; | |||
return <CompanyHoliday holidays={convertedHolidays as HolidaysList[]} />; | |||
}; | |||
CompanyHolidayWrapper.Loading = CompanyHolidayLoading; | |||
@@ -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(); | |||
@@ -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 */ | |||
} |