@@ -8,16 +8,36 @@ export interface InvoiceResult { | |||
id: number; | |||
projectCode: string; | |||
projectName: string; | |||
stage: String; | |||
comingPaymentMileStone: String; | |||
paymentMilestoneDate: String; | |||
stage: string; | |||
comingPaymentMileStone: string; | |||
paymentMilestoneDate: string; | |||
resourceUsage: number; | |||
unbilledHours: number; | |||
reminder: String; | |||
reminder: string; | |||
} | |||
export interface CreateInvoiceInputs { | |||
id: number; | |||
// Project Details | |||
projectCode: string; | |||
projectName: string; | |||
stage: string; | |||
comingPaymentMileStone: string; | |||
paymentMilestoneDate: string; | |||
resourceUsage: number; | |||
unbilledHours: number; | |||
// Invoice Info | |||
client: string; | |||
address: string; | |||
attention: string; | |||
invoiceDate: string; | |||
dueDate: string; | |||
projectRefNo: string; | |||
// Invoice related Info | |||
reminder: string; | |||
amount: number; | |||
billHours: number; | |||
} | |||
@@ -30,16 +50,18 @@ export interface InvoiceInformation{ | |||
invoiceDate: string; | |||
dueDate: string; | |||
projectRefNo: string; | |||
amount: number; | |||
} | |||
export const fetchProjectInvoiceById = cache(async (id: number) => { | |||
return serverFetchJson<InvoiceResult[]>(`${BASE_API_URL}/invoices/getProjectDetailById?id=${id}`, { | |||
return serverFetchJson<InvoiceResult[]>(`${BASE_API_URL}/invoices/getProjectDetail/${id}`, { | |||
next: { tags: ["projectDetailById"] }, | |||
}); | |||
}) | |||
export const fetchInvoiceInfoById = cache(async (id: number) => { | |||
return serverFetchJson<InvoiceInformation[]>(`${BASE_API_URL}/invoices/getInvoiceInfoById?id=${id}`, { | |||
return serverFetchJson<InvoiceInformation[]>(`${BASE_API_URL}/invoices/getInvoiceInfo/${id}`, { | |||
next: { tags: ["invoiceInfoById"] }, | |||
}); | |||
}) |
@@ -8,11 +8,11 @@ export interface InvoiceResult { | |||
projectCode: string; | |||
projectName: string; | |||
stage: String; | |||
comingPaymentMileStone: String; | |||
paymentMilestoneDate: String; | |||
comingPaymentMileStone: string; | |||
paymentMilestoneDate: string; | |||
resourceUsage: number; | |||
unbilledHours: number; | |||
reminder: String; | |||
reminder: string; | |||
} | |||
export interface InvoiceInformatio{ | |||
@@ -20,6 +20,9 @@ import { InvoiceResult } from "@/app/api/invoices"; | |||
import { InvoiceInformation, fetchInvoiceInfoById, fetchProjectInvoiceById } from "@/app/api/invoices/actions"; | |||
import InvoiceDetails from "./InvoiceDetails"; | |||
import ProjectDetails from "./ProjectDetails"; | |||
import ProjectTotalFee from "./ProjectTotalFee"; | |||
import { timestampToDateString } from "@/app/utils/formatUtil"; | |||
import dayjs from "dayjs"; | |||
const CreateInvoice: React.FC = ({ | |||
}) => { | |||
@@ -31,13 +34,18 @@ const CreateInvoice: React.FC = ({ | |||
const [invoiceDetail, setInvoiceDetail] = useState<InvoiceInformation>() | |||
const [serverError, setServerError] = useState(""); | |||
// const { getValues } = useForm(); | |||
const fetchProjectDetails = async () =>{ | |||
const projectId = searchParams.get("id") | |||
try{ | |||
if (projectId !== null && parseInt(projectId) > 0) { | |||
const projectDetail = await fetchProjectInvoiceById(parseInt(projectId)) | |||
console.log(projectDetail) | |||
setProjectDetail(projectDetail[0]) | |||
// console.log(projectDetail) | |||
const updatedPrijectDetail = projectDetail.map(detail => { | |||
return { ...detail, paymentMilestoneDate: timestampToDateString(detail.paymentMilestoneDate) }; // Update the age of person with id 2 | |||
}); | |||
setProjectDetail(updatedPrijectDetail[0]) | |||
} | |||
} catch (error){ | |||
console.log(error) | |||
@@ -51,7 +59,13 @@ const CreateInvoice: React.FC = ({ | |||
if (projectId !== null && parseInt(projectId) > 0) { | |||
const invoiceInfo = await fetchInvoiceInfoById(parseInt(projectId)) | |||
console.log(invoiceInfo) | |||
setInvoiceDetail(invoiceInfo[0]) | |||
const updatedInvoiceInfo = invoiceInfo.map(info => { | |||
return { ...info, | |||
invoiceDate: dayjs.unix(parseFloat(info.invoiceDate)/1000).format('YYYY/MM/DD'), | |||
dueDate: dayjs.unix(parseFloat(info.dueDate)/1000).format('YYYY/MM/DD') | |||
}; | |||
}); | |||
setInvoiceDetail(updatedInvoiceInfo[0]) | |||
} | |||
} catch (error){ | |||
console.log(error) | |||
@@ -69,6 +83,7 @@ const CreateInvoice: React.FC = ({ | |||
}; | |||
const handlePrintout = () => { | |||
// const formData = getValues(); | |||
console.log("Printing in Progress") | |||
} | |||
@@ -114,6 +129,9 @@ const CreateInvoice: React.FC = ({ | |||
{ | |||
invoiceDetail && <InvoiceDetails invoiceinfo={invoiceDetail}/> | |||
} | |||
{ | |||
<ProjectTotalFee /> | |||
} | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
variant="outlined" | |||
@@ -17,9 +17,12 @@ import RestartAlt from "@mui/icons-material/RestartAlt"; | |||
import Button from "@mui/material/Button"; | |||
import { Controller, UseFormRegister, useFormContext } from "react-hook-form"; | |||
import { CreateInvoiceInputs } from "@/app/api/invoices/actions"; | |||
import { TimePicker } from "@mui/x-date-pickers"; | |||
import dayjs from 'dayjs'; | |||
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers"; | |||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
import dayjs from "dayjs"; | |||
import { InvoiceInformation } from "@/app/api/invoices/actions"; | |||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||
interface Props { | |||
invoiceinfo: InvoiceInformation | |||
@@ -28,16 +31,24 @@ interface Props { | |||
const InvoiceDetails: React.FC<Props> = ({ | |||
invoiceinfo, | |||
}) => { | |||
const { t } = useTranslation(); | |||
const { | |||
t, | |||
i18n: { language }, | |||
} = useTranslation(); | |||
const { | |||
register, | |||
formState: { errors }, | |||
control, | |||
setValue, | |||
getValues, | |||
} = useFormContext<CreateInvoiceInputs>(); | |||
console.log(invoiceinfo) | |||
} = useFormContext<InvoiceInformation>(); | |||
useEffect(() => { | |||
// const invoiceDate = getValues("invoiceDate"); | |||
// const dueDate = getValues("dueDate"); | |||
setValue("invoiceDate", dayjs(invoiceinfo.invoiceDate).format(INPUT_DATE_FORMAT)) | |||
setValue("dueDate", dayjs(invoiceinfo.dueDate).format(INPUT_DATE_FORMAT)) | |||
}, [invoiceinfo,]); | |||
return ( | |||
<Card> | |||
@@ -46,6 +57,10 @@ const InvoiceDetails: React.FC<Props> = ({ | |||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||
{t("Invoice Information")} | |||
</Typography> | |||
<LocalizationProvider | |||
dateAdapter={AdapterDayjs} | |||
adapterLocale={`${language}-hk`} | |||
> | |||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
<Grid item xs={6}> | |||
<TextField | |||
@@ -72,18 +87,28 @@ const InvoiceDetails: React.FC<Props> = ({ | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
<FormControl fullWidth> | |||
<DatePicker | |||
label={t("Invoice Date")} | |||
fullWidth | |||
defaultValue={invoiceinfo.invoiceDate} | |||
value={dayjs(invoiceinfo.invoiceDate)} | |||
onChange={(date) => { | |||
if (!date) return; | |||
setValue("invoiceDate", date.format(INPUT_DATE_FORMAT)); | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
<FormControl fullWidth> | |||
<DatePicker | |||
label={t("Due Date")} | |||
fullWidth | |||
defaultValue={invoiceinfo.dueDate} | |||
value={dayjs(invoiceinfo.dueDate)} | |||
onChange={(date) => { | |||
if (!date) return; | |||
setValue("dueDate", date.format(INPUT_DATE_FORMAT)); | |||
}} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
@@ -103,8 +128,8 @@ const InvoiceDetails: React.FC<Props> = ({ | |||
error={Boolean(errors.amount)} | |||
/> | |||
</Grid> | |||
</Grid> | |||
</LocalizationProvider> | |||
</Box> | |||
{/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||
<Button variant="text" startIcon={<RestartAlt />}> | |||
@@ -14,6 +14,7 @@ import Typography from "@mui/material/Typography"; | |||
import { useTranslation } from "react-i18next"; | |||
import { InvoiceResult } from "@/app/api/invoices"; | |||
import { useFormContext } from "react-hook-form"; | |||
import { timestampToDateString } from "@/app/utils/formatUtil"; | |||
interface Props { | |||
projectDetails: InvoiceResult | |||
@@ -0,0 +1,46 @@ | |||
import { CreateInvoiceInputs } from "@/app/api/invoices/actions"; | |||
import { CreateProjectInputs } from "@/app/api/projects/actions"; | |||
import { TaskGroup } from "@/app/api/tasks"; | |||
import { moneyFormatter } from "@/app/utils/formatUtil"; | |||
import { Divider, Stack, Typography } from "@mui/material"; | |||
import React from "react"; | |||
import { useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
const ProjectTotalFee: React.FC= ({}) => { | |||
const { t } = useTranslation(); | |||
const { watch } = useFormContext<CreateInvoiceInputs>(); | |||
const amount = watch("amount"); | |||
let projectTotal = 0; | |||
return ( | |||
<Stack spacing={1}> | |||
{/* {taskGroups.map((group, index) => { | |||
const payments = milestones[group.id]?.payments || []; | |||
const paymentTotal = payments.reduce((acc, p) => acc + p.amount, 0); | |||
projectTotal += paymentTotal; | |||
return ( | |||
<Stack | |||
key={`${group.id}-${index}`} | |||
direction="row" | |||
justifyContent="space-between" | |||
> | |||
<Typography variant="subtitle2">{group.name}</Typography> | |||
<Typography>{moneyFormatter.format(paymentTotal)}</Typography> | |||
</Stack> | |||
); | |||
})} */} | |||
<Divider sx={{ paddingBlockStart: 2 }} /> | |||
<Stack direction="row" justifyContent="space-between"> | |||
<Typography variant="h6">{t("Project Total Fee")}</Typography> | |||
<Typography>{moneyFormatter.format(projectTotal += amount)}</Typography> | |||
</Stack> | |||
</Stack> | |||
); | |||
}; | |||
export default ProjectTotalFee; |