@@ -1,4 +1,4 @@ | |||
import ClaimDetail from "@/components/ClaimDetail"; | |||
import ClaimSave from "@/components/ClaimSave"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
import Typography from "@mui/material/Typography"; | |||
import { Metadata } from "next"; | |||
@@ -14,7 +14,7 @@ const ClaimDetails: React.FC = async () => { | |||
<> | |||
<Typography variant="h4">{t("Create Claim")}</Typography> | |||
<I18nProvider namespaces={["claim", "common"]}> | |||
<ClaimDetail /> | |||
<ClaimSave /> | |||
</I18nProvider> | |||
</> | |||
); | |||
@@ -1,18 +1,22 @@ | |||
"use server"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { Task, TaskGroup } from "../tasks"; | |||
import { Customer } from "../customer"; | |||
import { revalidateTag } from "next/cache"; | |||
export interface CreateProjectInputs { | |||
// Project details | |||
// Project | |||
projectId: number | null; | |||
projectDeleted: boolean | null; | |||
projectCode: string; | |||
projectName: string; | |||
projectCategoryId: number; | |||
projectDescription: string; | |||
projectLeadId: number; | |||
projectActualStart: string; | |||
projectActualEnd: string; | |||
// Project info | |||
serviceTypeId: number; | |||
@@ -62,8 +66,16 @@ export interface PaymentInputs { | |||
amount: number; | |||
} | |||
export interface CreateProjectResponse { | |||
id: number, | |||
name: string, | |||
code: string, | |||
category: string, | |||
team: string, | |||
client: string, | |||
} | |||
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", | |||
body: JSON.stringify(data), | |||
headers: { "Content-Type": "application/json" }, | |||
@@ -72,3 +84,15 @@ export const saveProject = async (data: CreateProjectInputs) => { | |||
revalidateTag("projects"); | |||
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 | |||
}; |
@@ -1 +0,0 @@ | |||
export { default } from "./ClaimDetailWrapper"; |
@@ -21,7 +21,7 @@ export interface Props { | |||
projectCombo: ProjectCombo[] | |||
} | |||
const ClaimDetail: React.FC<Props> = ({ projectCombo }) => { | |||
const ClaimSave: React.FC<Props> = ({ projectCombo }) => { | |||
const { t } = useTranslation("common"); | |||
const [serverError, setServerError] = useState(""); | |||
const router = useRouter(); | |||
@@ -74,15 +74,15 @@ const ClaimDetail: React.FC<Props> = ({ projectCombo }) => { | |||
const buttonName = (event?.nativeEvent as any).submitter.name | |||
const formData = new FormData() | |||
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++) { | |||
// const updatedData = { | |||
@@ -155,4 +155,4 @@ const ClaimDetail: React.FC<Props> = ({ projectCombo }) => { | |||
); | |||
}; | |||
export default ClaimDetail; | |||
export default ClaimSave; |
@@ -1,6 +1,6 @@ | |||
import React from "react"; | |||
import ClaimDetail from "./ClaimDetail"; | |||
import ClaimSave from "./ClaimSave"; | |||
import { fetchProjectCombo } from "@/app/api/claims"; | |||
// import TaskSetup from "./TaskSetup"; | |||
// import StaffAllocation from "./StaffAllocation"; | |||
@@ -13,7 +13,7 @@ const ClaimDetailWrapper: React.FC = async () => { | |||
]); | |||
return ( | |||
<ClaimDetail projectCombo={projectCombo}/> | |||
<ClaimSave projectCombo={projectCombo}/> | |||
); | |||
}; | |||
@@ -0,0 +1 @@ | |||
export { default } from "./ClaimSaveWrapper"; |
@@ -1,5 +1,6 @@ | |||
"use client"; | |||
import DoneIcon from '@mui/icons-material/Done' | |||
import Check from "@mui/icons-material/Check"; | |||
import Close from "@mui/icons-material/Close"; | |||
import Button from "@mui/material/Button"; | |||
@@ -21,7 +22,7 @@ import { | |||
SubmitHandler, | |||
useForm, | |||
} 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 { | |||
BuildingType, | |||
@@ -37,6 +38,8 @@ import { Typography } from "@mui/material"; | |||
import { Grade } from "@/app/api/grades"; | |||
import { Customer, Subsidiary } from "@/app/api/customer"; | |||
import { isEmpty } from "lodash"; | |||
import { deleteDialog, errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||
import dayjs from "dayjs"; | |||
export interface Props { | |||
isEditMode: boolean; | |||
@@ -98,6 +101,19 @@ const CreateProject: React.FC<Props> = ({ | |||
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"]>>( | |||
(_e, newValue) => { | |||
setTabIndex(newValue); | |||
@@ -106,15 +122,48 @@ const CreateProject: React.FC<Props> = ({ | |||
); | |||
const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>( | |||
async (data) => { | |||
async (data, event) => { | |||
try { | |||
console.log("first") | |||
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) { | |||
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: isEmpty(defaultInputs?.manhourPercentageByGrade) | |||
? 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 ( | |||
<> | |||
{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}> | |||
<Stack | |||
spacing={2} | |||
component="form" | |||
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 | |||
value={tabIndex} | |||
onChange={handleTabChange} | |||
@@ -238,7 +290,7 @@ const CreateProject: React.FC<Props> = ({ | |||
> | |||
{t("Cancel")} | |||
</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")} | |||
</Button> | |||
</Stack> | |||
@@ -199,20 +199,20 @@ const CustomerSave: React.FC<Props> = ({ | |||
setServerError(""); | |||
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) { | |||
console.log(e) | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
@@ -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 result = await Swal.fire({ | |||
icon: "question", | |||
title: t("Do you want to submit?"), | |||
title: props.title ?? t("Do you want to submit?"), | |||
cancelButtonText: t("Cancel"), | |||
confirmButtonText: t("Submit"), | |||
confirmButtonText: props.confirmButtonText ?? t("Submit"), | |||
showCancelButton: true, | |||
showConfirmButton: true, | |||
}); | |||