@@ -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> | ||||
</> | </> | ||||
); | ); | ||||
@@ -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 | |||||
}; |
@@ -1 +0,0 @@ | |||||
export { default } from "./ClaimDetailWrapper"; |
@@ -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; |
@@ -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}/> | |||||
); | ); | ||||
}; | }; | ||||
@@ -0,0 +1 @@ | |||||
export { default } from "./ClaimSaveWrapper"; |
@@ -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> | ||||
@@ -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.")); | ||||
@@ -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, | ||||
}); | }); | ||||