2. Add Invoice search pagetags/Baseline_30082024_FRONTEND_UAT
@@ -0,0 +1,25 @@ | |||||
import { Metadata } from "next"; | |||||
import { getServerI18n } from "@/i18n"; | |||||
import Add from "@mui/icons-material/Add"; | |||||
import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import Link from "next/link"; | |||||
export const metadata: Metadata = { | |||||
title: "Create Invoice", | |||||
}; | |||||
const Invoice: React.FC = async () => { | |||||
const { t } = await getServerI18n("Create Invoice"); | |||||
return ( | |||||
<> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Create Invoice")} | |||||
</Typography> | |||||
</> | |||||
) | |||||
}; | |||||
export default Invoice; |
@@ -1,11 +1,40 @@ | |||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import { getServerI18n } from "@/i18n"; | |||||
import Add from "@mui/icons-material/Add"; | |||||
import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import Link from "next/link"; | |||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Invoice", | title: "Invoice", | ||||
}; | }; | ||||
const Invoice: React.FC = async () => { | const Invoice: React.FC = async () => { | ||||
return "Invoice"; | |||||
const { t } = await getServerI18n("Invoice"); | |||||
return ( | |||||
<> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="space-between" | |||||
flexWrap="wrap" | |||||
rowGap={2} | |||||
> | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Invoice")} | |||||
</Typography> | |||||
<Button | |||||
variant="contained" | |||||
startIcon={<Add />} | |||||
LinkComponent={Link} | |||||
href="/invoice/new" | |||||
> | |||||
{t("Create Invoice")} | |||||
</Button> | |||||
</Stack> | |||||
</> | |||||
) | |||||
}; | }; | ||||
export default Invoice; | export default Invoice; |
@@ -1,20 +1,22 @@ | |||||
import { fetchProjectCategories } from "@/app/api/projects"; | |||||
import { preloadStaff } from "@/app/api/staff"; | |||||
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | |||||
import CreateProject from "@/components/CreateProject"; | |||||
import CreateCompany from "@/components/CreateCompany"; | |||||
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"; | ||||
export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
title: "Create Project", | |||||
title: "Create Comapny", | |||||
}; | }; | ||||
const Companys: React.FC = async () => { | const Companys: React.FC = async () => { | ||||
const { t } = await getServerI18n("projects"); | |||||
const { t } = await getServerI18n("companys"); | |||||
return( | return( | ||||
<>AAAA</> | |||||
<> | |||||
<Typography variant="h4">{t("Create Company")}</Typography> | |||||
<I18nProvider namespaces={["companys"]}> | |||||
<CreateCompany /> | |||||
</I18nProvider> | |||||
</> | |||||
) | ) | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
"use server"; | "use server"; | ||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | import { serverFetchJson } from "@/app/utils/fetchUtil"; | ||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { cache } from "react"; | import { cache } from "react"; | ||||
@@ -12,6 +13,30 @@ export interface combo { | |||||
records: comboProp[]; | records: comboProp[]; | ||||
} | } | ||||
export interface CreateCompanyInputs { | |||||
companyCode: String; | |||||
companyName: String; | |||||
brNo: String; | |||||
contactName: String; | |||||
phone: String; | |||||
otHourTo: String; | |||||
otHourFrom: String; | |||||
normalHourTo: String; | |||||
normalHourFrom: String; | |||||
currency: String; | |||||
address: String; | |||||
distract: String; | |||||
email: String; | |||||
} | |||||
export const saveCompany = async (data: CreateCompanyInputs) => { | |||||
return serverFetchJson(`${BASE_API_URL}/companys/new`, { | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
}; | |||||
export const fetchCompanyCombo = cache(async () => { | export const fetchCompanyCombo = cache(async () => { | ||||
return serverFetchJson<combo>(`${BASE_API_URL}/companys/combo`, { | return serverFetchJson<combo>(`${BASE_API_URL}/companys/combo`, { | ||||
next: { tags: ["company"] }, | next: { tags: ["company"] }, | ||||
@@ -0,0 +1,110 @@ | |||||
"use client"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import Box from "@mui/material/Box"; | |||||
import Card from "@mui/material/Card"; | |||||
import CardContent from "@mui/material/CardContent"; | |||||
import FormControl from "@mui/material/FormControl"; | |||||
import Grid from "@mui/material/Grid"; | |||||
import InputLabel from "@mui/material/InputLabel"; | |||||
import MenuItem from "@mui/material/MenuItem"; | |||||
import Select from "@mui/material/Select"; | |||||
import TextField from "@mui/material/TextField"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import CardActions from "@mui/material/CardActions"; | |||||
import RestartAlt from "@mui/icons-material/RestartAlt"; | |||||
import Button from "@mui/material/Button"; | |||||
import { Controller, useFormContext } from "react-hook-form"; | |||||
import { CreateCompanyInputs } from "@/app/api/companys/actions"; | |||||
const CompanyDetails: React.FC = ({ | |||||
}) => { | |||||
const { t } = useTranslation(); | |||||
const { | |||||
register, | |||||
formState: { errors }, | |||||
control, | |||||
} = useFormContext<CreateCompanyInputs>(); | |||||
return ( | |||||
<Card> | |||||
<CardContent component={Stack} spacing={4}> | |||||
<Box> | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Company Details")} | |||||
</Typography> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company Code")} | |||||
fullWidth | |||||
{...register("companyCode", { | |||||
required: "Company code required!", | |||||
})} | |||||
error={Boolean(errors.companyCode)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company Name")} | |||||
fullWidth | |||||
{...register("companyName", { | |||||
required: "Company name required!", | |||||
})} | |||||
error={Boolean(errors.companyName)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company brNo")} | |||||
fullWidth | |||||
{...register("brNo", { | |||||
required: "Please enter a brNo", | |||||
})} | |||||
error={Boolean(errors.brNo)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company contact name")} | |||||
fullWidth | |||||
{...register("contactName", { | |||||
required: "Please enter a contact name", | |||||
})} | |||||
error={Boolean(errors.contactName)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company contact number")} | |||||
fullWidth | |||||
{...register("phone", { | |||||
required: "Please enter a contact number", | |||||
})} | |||||
error={Boolean(errors.phone)} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Company contact email")} | |||||
fullWidth | |||||
{...register("email", { | |||||
required: "Please enter a contact email", | |||||
})} | |||||
error={Boolean(errors.email)} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | |||||
{/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
<Button variant="text" startIcon={<RestartAlt />}> | |||||
{t("Reset")} | |||||
</Button> | |||||
</CardActions> */} | |||||
</CardContent> | |||||
</Card> | |||||
); | |||||
}; | |||||
export default CompanyDetails; |
@@ -0,0 +1,101 @@ | |||||
"use client"; | |||||
import Check from "@mui/icons-material/Check"; | |||||
import Close from "@mui/icons-material/Close"; | |||||
import Button from "@mui/material/Button"; | |||||
import Stack from "@mui/material/Stack"; | |||||
import { CreateCompanyInputs, saveCompany } from "@/app/api/companys/actions"; | |||||
import { useRouter } from "next/navigation"; | |||||
import React, { useCallback, useState } from "react"; | |||||
import { useTranslation } from "react-i18next"; | |||||
import { | |||||
FieldErrors, | |||||
FormProvider, | |||||
SubmitErrorHandler, | |||||
SubmitHandler, | |||||
useForm, | |||||
} from "react-hook-form"; | |||||
import CompanyDetails from "./CompanyDetails"; | |||||
const CreateCompany: React.FC = ({ | |||||
}) => { | |||||
const [serverError, setServerError] = useState(""); | |||||
const { t } = useTranslation(); | |||||
const router = useRouter(); | |||||
const handleCancel = () => { | |||||
router.back(); | |||||
}; | |||||
const onSubmit = useCallback<SubmitHandler<CreateCompanyInputs>>( | |||||
async (data) => { | |||||
try { | |||||
console.log(data); | |||||
setServerError(""); | |||||
// console.log(JSON.stringify(data)); | |||||
await saveCompany(data) | |||||
router.replace("/settings/companys"); | |||||
} catch (e) { | |||||
setServerError(t("An error has occurred. Please try again later.")); | |||||
} | |||||
}, | |||||
[router, t], | |||||
); | |||||
const onSubmitError = useCallback<SubmitErrorHandler<CreateCompanyInputs>>( | |||||
(errors) => { | |||||
console.log(errors) | |||||
}, | |||||
[], | |||||
); | |||||
const formProps = useForm<CreateCompanyInputs>({ | |||||
defaultValues: { | |||||
companyCode: "", | |||||
companyName: "", | |||||
brNo: "", | |||||
contactName: "", | |||||
phone: "", | |||||
otHourTo: "", | |||||
otHourFrom: "", | |||||
normalHourTo: "", | |||||
normalHourFrom: "", | |||||
currency: "", | |||||
address: "", | |||||
distract: "", | |||||
email: "", | |||||
}, | |||||
}); | |||||
const errors = formProps.formState.errors; | |||||
return( | |||||
<FormProvider {...formProps}> | |||||
<Stack | |||||
spacing={2} | |||||
component="form" | |||||
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||||
> | |||||
{ | |||||
<CompanyDetails /> | |||||
} | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
variant="outlined" | |||||
startIcon={<Close />} | |||||
onClick={handleCancel} | |||||
> | |||||
{t("Cancel")} | |||||
</Button> | |||||
<Button variant="contained" startIcon={<Check />} type="submit"> | |||||
{t("Confirm")} | |||||
</Button> | |||||
</Stack> | |||||
</Stack> | |||||
</FormProvider> | |||||
) | |||||
} | |||||
export default CreateCompany; |
@@ -0,0 +1,10 @@ | |||||
import CreateCompany from "./CreateCompany"; | |||||
const CreateCompanyWrapper: React.FC = async () => { | |||||
return ( | |||||
<CreateCompany | |||||
/> | |||||
) | |||||
} | |||||
export default CreateCompanyWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./CreateCompanyWrapper" |