Ver a proveniência

1. Company Holiday month view and list view

2. Update Swal class, to be alwasy on top
tags/Baseline_30082024_FRONTEND_UAT
MSI\2Fi há 1 ano
ascendente
cometimento
65f6987b8e
8 ficheiros alterados com 166 adições e 26 eliminações
  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 Ver ficheiro

@@ -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 Ver ficheiro

@@ -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"] },
});
});

+ 6
- 0
src/app/utils/formatUtil.ts Ver ficheiro

@@ -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", {


+ 74
- 14
src/components/CompanyHoliday/CompanyHoliday.tsx Ver ficheiro

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


+ 15
- 7
src/components/CompanyHoliday/CompanyHolidayDialog.tsx Ver ficheiro

@@ -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: {


+ 12
- 2
src/components/CompanyHoliday/CompanyHolidayWrapper.tsx Ver ficheiro

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


+ 9
- 0
src/components/Swal/CustomAlerts.js Ver ficheiro

@@ -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();


+ 7
- 0
src/components/Swal/sweetalert2.css Ver ficheiro

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

Carregando…
Cancelar
Guardar