瀏覽代碼

update project

tags/Baseline_30082024_FRONTEND_UAT
cyril.tsui 1 年之前
父節點
當前提交
93f73b4df9
共有 11 個文件被更改,包括 133 次插入57 次删除
  1. +2
    -2
      src/app/(main)/staffReimbursement/create/page.tsx
  2. +27
    -3
      src/app/api/projects/actions.ts
  3. +0
    -1
      src/components/ClaimDetail/index.ts
  4. +0
    -0
      src/components/ClaimSave/ClaimFormInfo.tsx
  5. +0
    -0
      src/components/ClaimSave/ClaimFormInputGrid.tsx
  6. +11
    -11
      src/components/ClaimSave/ClaimSave.tsx
  7. +2
    -2
      src/components/ClaimSave/ClaimSaveWrapper.tsx
  8. +1
    -0
      src/components/ClaimSave/index.ts
  9. +73
    -21
      src/components/CreateProject/CreateProject.tsx
  10. +14
    -14
      src/components/CustomerSave/CustomerSave.tsx
  11. +3
    -3
      src/components/Swal/CustomAlerts.js

+ 2
- 2
src/app/(main)/staffReimbursement/create/page.tsx 查看文件

@@ -1,4 +1,4 @@
import ClaimDetail from "@/components/ClaimDetail";
import ClaimSave from "@/components/ClaimSave";
import { I18nProvider, getServerI18n } from "@/i18n"; import { I18nProvider, getServerI18n } from "@/i18n";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { Metadata } from "next"; import { Metadata } from "next";
@@ -14,7 +14,7 @@ const ClaimDetails: React.FC = async () => {
<> <>
<Typography variant="h4">{t("Create Claim")}</Typography> <Typography variant="h4">{t("Create Claim")}</Typography>
<I18nProvider namespaces={["claim", "common"]}> <I18nProvider namespaces={["claim", "common"]}>
<ClaimDetail />
<ClaimSave />
</I18nProvider> </I18nProvider>
</> </>
); );


+ 27
- 3
src/app/api/projects/actions.ts 查看文件

@@ -1,18 +1,22 @@
"use server"; "use server";


import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api"; import { BASE_API_URL } from "@/config/api";
import { Task, TaskGroup } from "../tasks"; import { Task, TaskGroup } from "../tasks";
import { Customer } from "../customer"; import { Customer } from "../customer";
import { revalidateTag } from "next/cache"; import { revalidateTag } from "next/cache";


export interface CreateProjectInputs { export interface CreateProjectInputs {
// Project details
// Project
projectId: number | null;
projectDeleted: boolean | null;
projectCode: string; projectCode: string;
projectName: string; projectName: string;
projectCategoryId: number; projectCategoryId: number;
projectDescription: string; projectDescription: string;
projectLeadId: number; projectLeadId: number;
projectActualStart: string;
projectActualEnd: string;


// Project info // Project info
serviceTypeId: number; serviceTypeId: number;
@@ -62,8 +66,16 @@ export interface PaymentInputs {
amount: number; amount: number;
} }


export interface CreateProjectResponse {
id: number,
name: string,
code: string,
category: string,
team: string,
client: string,
}
export const saveProject = async (data: CreateProjectInputs) => { export const saveProject = async (data: CreateProjectInputs) => {
const newProject = await serverFetchJson(`${BASE_API_URL}/projects/new`, {
const newProject = await serverFetchJson<CreateProjectResponse>(`${BASE_API_URL}/projects/new`, {
method: "POST", method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -72,3 +84,15 @@ export const saveProject = async (data: CreateProjectInputs) => {
revalidateTag("projects"); revalidateTag("projects");
return newProject; return newProject;
}; };

export const deleteProject = async (id: number) => {
const project = await serverFetchWithNoContent(
`${BASE_API_URL}/projects/${id}`,
{
method: "DELETE",
headers: { "Content-Type": "application/json" },
},
);

return project
};

+ 0
- 1
src/components/ClaimDetail/index.ts 查看文件

@@ -1 +0,0 @@
export { default } from "./ClaimDetailWrapper";

src/components/ClaimDetail/ClaimFormInfo.tsx → src/components/ClaimSave/ClaimFormInfo.tsx 查看文件


src/components/ClaimDetail/ClaimFormInputGrid.tsx → src/components/ClaimSave/ClaimFormInputGrid.tsx 查看文件


src/components/ClaimDetail/ClaimDetail.tsx → src/components/ClaimSave/ClaimSave.tsx 查看文件

@@ -21,7 +21,7 @@ export interface Props {
projectCombo: ProjectCombo[] projectCombo: ProjectCombo[]
} }


const ClaimDetail: React.FC<Props> = ({ projectCombo }) => {
const ClaimSave: React.FC<Props> = ({ projectCombo }) => {
const { t } = useTranslation("common"); const { t } = useTranslation("common");
const [serverError, setServerError] = useState(""); const [serverError, setServerError] = useState("");
const router = useRouter(); const router = useRouter();
@@ -74,15 +74,15 @@ const ClaimDetail: React.FC<Props> = ({ projectCombo }) => {
const buttonName = (event?.nativeEvent as any).submitter.name const buttonName = (event?.nativeEvent as any).submitter.name
const formData = new FormData() const formData = new FormData()
formData.append("expenseType", data.expenseType) formData.append("expenseType", data.expenseType)
data.addClaimDetails.forEach((claimDetail) => {
console.log(claimDetail)
formData.append("addClaimDetailIds", JSON.stringify(claimDetail.id))
formData.append("addClaimDetailInvoiceDates", convertDateToString(claimDetail.invoiceDate, "YYYY-MM-DD"))
formData.append("addClaimDetailProjectIds", JSON.stringify(claimDetail.project))
formData.append("addClaimDetailDescriptions", claimDetail.description)
formData.append("addClaimDetailAmounts", JSON.stringify(claimDetail.amount))
formData.append("addClaimDetailNewSupportingDocuments", claimDetail.newSupportingDocument)
formData.append("addClaimDetailOldSupportingDocumentIds", JSON.stringify(claimDetail?.oldSupportingDocument?.id ?? -1))
data.addClaimDetails.forEach((ClaimSave) => {
console.log(ClaimSave)
formData.append("addClaimDetailIds", JSON.stringify(ClaimSave.id))
formData.append("addClaimDetailInvoiceDates", convertDateToString(ClaimSave.invoiceDate, "YYYY-MM-DD"))
formData.append("addClaimDetailProjectIds", JSON.stringify(ClaimSave.project))
formData.append("addClaimDetailDescriptions", ClaimSave.description)
formData.append("addClaimDetailAmounts", JSON.stringify(ClaimSave.amount))
formData.append("addClaimDetailNewSupportingDocuments", ClaimSave.newSupportingDocument)
formData.append("addClaimDetailOldSupportingDocumentIds", JSON.stringify(ClaimSave?.oldSupportingDocument?.id ?? -1))
}) })
// for (let i = 0; i < data.addClaimDetails.length; i++) { // for (let i = 0; i < data.addClaimDetails.length; i++) {
// const updatedData = { // const updatedData = {
@@ -155,4 +155,4 @@ const ClaimDetail: React.FC<Props> = ({ projectCombo }) => {
); );
}; };


export default ClaimDetail;
export default ClaimSave;

src/components/ClaimDetail/ClaimDetailWrapper.tsx → src/components/ClaimSave/ClaimSaveWrapper.tsx 查看文件

@@ -1,6 +1,6 @@


import React from "react"; import React from "react";
import ClaimDetail from "./ClaimDetail";
import ClaimSave from "./ClaimSave";
import { fetchProjectCombo } from "@/app/api/claims"; import { fetchProjectCombo } from "@/app/api/claims";
// import TaskSetup from "./TaskSetup"; // import TaskSetup from "./TaskSetup";
// import StaffAllocation from "./StaffAllocation"; // import StaffAllocation from "./StaffAllocation";
@@ -13,7 +13,7 @@ const ClaimDetailWrapper: React.FC = async () => {
]); ]);


return ( return (
<ClaimDetail projectCombo={projectCombo}/>
<ClaimSave projectCombo={projectCombo}/>
); );
}; };



+ 1
- 0
src/components/ClaimSave/index.ts 查看文件

@@ -0,0 +1 @@
export { default } from "./ClaimSaveWrapper";

+ 73
- 21
src/components/CreateProject/CreateProject.tsx 查看文件

@@ -1,5 +1,6 @@
"use client"; "use client";


import DoneIcon from '@mui/icons-material/Done'
import Check from "@mui/icons-material/Check"; import Check from "@mui/icons-material/Check";
import Close from "@mui/icons-material/Close"; import Close from "@mui/icons-material/Close";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@@ -21,7 +22,7 @@ import {
SubmitHandler, SubmitHandler,
useForm, useForm,
} from "react-hook-form"; } from "react-hook-form";
import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions";
import { CreateProjectInputs, deleteProject, saveProject } from "@/app/api/projects/actions";
import { Delete, Error, PlayArrow } from "@mui/icons-material"; import { Delete, Error, PlayArrow } from "@mui/icons-material";
import { import {
BuildingType, BuildingType,
@@ -37,6 +38,8 @@ import { Typography } from "@mui/material";
import { Grade } from "@/app/api/grades"; import { Grade } from "@/app/api/grades";
import { Customer, Subsidiary } from "@/app/api/customer"; import { Customer, Subsidiary } from "@/app/api/customer";
import { isEmpty } from "lodash"; import { isEmpty } from "lodash";
import { deleteDialog, errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts";
import dayjs from "dayjs";


export interface Props { export interface Props {
isEditMode: boolean; isEditMode: boolean;
@@ -98,6 +101,19 @@ const CreateProject: React.FC<Props> = ({
router.replace("/projects"); router.replace("/projects");
}; };


const handleDelete = () => {
deleteDialog(async () => {
await deleteProject(formProps.getValues("projectId")!!)

const clickSuccessDialog = await successDialog("Delete Success", t)

if (clickSuccessDialog) {
router.replace("/projects");
}

}, t)
}

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => { (_e, newValue) => {
setTabIndex(newValue); setTabIndex(newValue);
@@ -106,15 +122,48 @@ const CreateProject: React.FC<Props> = ({
); );


const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>( const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>(
async (data) => {
async (data, event) => {
try { try {
console.log("first")
setServerError(""); setServerError("");
if (isEditMode) {
console.log("edit project", data);
} else {
await saveProject(data);

let title = t("Do you want to submit?")
let confirmButtonText = t("Submit")
let successTitle = t("Submit Success")
let errorTitle = t("Submit Fail")
const buttonName = (event?.nativeEvent as any).submitter.name

if (buttonName === "start") {
title = t("Do you want to start?")
confirmButtonText = t("Start")
successTitle = t("Start Success")
errorTitle = t("Start Fail")
} else if (buttonName === "complete") {
title = t("Do you want to complete?")
confirmButtonText = t("Complete")
successTitle = t("Complete Success")
errorTitle = t("Complete Fail")
} }
router.replace("/projects");

submitDialog(async () => {
if (buttonName === "start") {
data.projectActualStart = dayjs().format("YYYY-MM-DD")
} else if (buttonName === "complete") {
data.projectActualEnd = dayjs().format("YYYY-MM-DD")
}

const response = await saveProject(data);

if (response.id > 0) {
successDialog(successTitle, t).then(() => {
router.replace("/projects");
})
} else {
errorDialog(errorTitle, t).then(() => {
return false
})
}
}, t, { title: title, confirmButtonText: confirmButtonText })
} catch (e) { } catch (e) {
setServerError(t("An error has occurred. Please try again later.")); setServerError(t("An error has occurred. Please try again later."));
} }
@@ -147,9 +196,9 @@ const CreateProject: React.FC<Props> = ({
// manhourPercentageByGrade should have a sensible default // manhourPercentageByGrade should have a sensible default
manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade)
? grades.reduce((acc, grade) => { ? grades.reduce((acc, grade) => {
return { ...acc, [grade.id]: 1 / grades.length };
}, {})
: defaultInputs.manhourPercentageByGrade,
return { ...acc, [grade.id]: 1 / grades.length };
}, {})
: defaultInputs?.manhourPercentageByGrade,
}, },
}); });


@@ -157,22 +206,25 @@ const CreateProject: React.FC<Props> = ({


return ( return (
<> <>
{isEditMode && (
<Stack direction="row" gap={1}>
<Button variant="contained" startIcon={<PlayArrow />} color="success">
{t("Start Project")}
</Button>
<Button variant="outlined" startIcon={<Delete />} color="error">
{t("Delete Project")}
</Button>
</Stack>
)}
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Stack <Stack
spacing={2} spacing={2}
component="form" component="form"
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
> >
{isEditMode && !(formProps.getValues("projectDeleted") === true) && (
<Stack direction="row" gap={1}>
{!formProps.getValues("projectActualStart") && <Button name="start" type="submit" variant="contained" startIcon={<PlayArrow />} color="success">
{t("Start Project")}
</Button>}
{formProps.getValues("projectActualStart") && !formProps.getValues("projectActualEnd") && <Button name="complete" type="submit" variant="contained" startIcon={<DoneIcon />} color="info">
{t("Complete Project")}
</Button>}
{!(formProps.getValues("projectActualStart") && formProps.getValues("projectActualEnd")) && <Button variant="outlined" startIcon={<Delete />} color="error" onClick={handleDelete}>
{t("Delete Project")}
</Button>}
</Stack>
)}
<Tabs <Tabs
value={tabIndex} value={tabIndex}
onChange={handleTabChange} onChange={handleTabChange}
@@ -238,7 +290,7 @@ const CreateProject: React.FC<Props> = ({
> >
{t("Cancel")} {t("Cancel")}
</Button> </Button>
<Button variant="contained" startIcon={<Check />} type="submit">
<Button variant="contained" startIcon={<Check />} type="submit" disabled={formProps.getValues("projectDeleted") === true || (!!formProps.getValues("projectActualStart") && !!formProps.getValues("projectActualEnd"))}>
{isEditMode ? t("Save") : t("Confirm")} {isEditMode ? t("Save") : t("Confirm")}
</Button> </Button>
</Stack> </Stack>


+ 14
- 14
src/components/CustomerSave/CustomerSave.tsx 查看文件

@@ -199,20 +199,20 @@ const CustomerSave: React.FC<Props> = ({
setServerError(""); setServerError("");


submitDialog(async () => { submitDialog(async () => {
const response = await saveCustomer(data);
if (response.message === "Success") {
successDialog(t("Submit Success"), t).then(() => {
router.replace("/settings/customer");
})
} else {
errorDialog(t("Submit Fail"), t).then(() => {
formProps.setError("code", { message: response.message, type: "custom" })
setTabIndex(0)
return false
})
}
}, t)
const response = await saveCustomer(data);
if (response.message === "Success") {
successDialog(t("Submit Success"), t).then(() => {
router.replace("/settings/customer");
})
} else {
errorDialog(t("Submit Fail"), t).then(() => {
formProps.setError("code", { message: response.message, type: "custom" })
setTabIndex(0)
return false
})
}
}, t)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
setServerError(t("An error has occurred. Please try again later.")); setServerError(t("An error has occurred. Please try again later."));


+ 3
- 3
src/components/Swal/CustomAlerts.js 查看文件

@@ -50,13 +50,13 @@ export const warningDialog = (text, t) => {
}) })
} }


export const submitDialog = async (confirmAction, t) => {
export const submitDialog = async (confirmAction, t, {...props}) => {
// const { t } = useTranslation("common") // const { t } = useTranslation("common")
const result = await Swal.fire({ const result = await Swal.fire({
icon: "question", icon: "question",
title: t("Do you want to submit?"),
title: props.title ?? t("Do you want to submit?"),
cancelButtonText: t("Cancel"), cancelButtonText: t("Cancel"),
confirmButtonText: t("Submit"),
confirmButtonText: props.confirmButtonText ?? t("Submit"),
showCancelButton: true, showCancelButton: true,
showConfirmButton: true, showConfirmButton: true,
}); });


Loading…
取消
儲存