@@ -1,4 +1,12 @@ | |||||
import { fetchProjectCategories } from "@/app/api/projects"; | |||||
import { | |||||
fetchProjectBuildingTypes, | |||||
fetchProjectCategories, | |||||
fetchProjectContractTypes, | |||||
fetchProjectFundingTypes, | |||||
fetchProjectLocationTypes, | |||||
fetchProjectServiceTypes, | |||||
fetchProjectWorkNatures, | |||||
} from "@/app/api/projects"; | |||||
import { preloadStaff } from "@/app/api/staff"; | import { preloadStaff } from "@/app/api/staff"; | ||||
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | ||||
import CreateProject from "@/components/CreateProject"; | import CreateProject from "@/components/CreateProject"; | ||||
@@ -17,6 +25,12 @@ const Projects: React.FC = async () => { | |||||
fetchAllTasks(); | fetchAllTasks(); | ||||
fetchTaskTemplates(); | fetchTaskTemplates(); | ||||
fetchProjectCategories(); | fetchProjectCategories(); | ||||
fetchProjectContractTypes(); | |||||
fetchProjectFundingTypes(); | |||||
fetchProjectLocationTypes(); | |||||
fetchProjectServiceTypes(); | |||||
fetchProjectBuildingTypes(); | |||||
fetchProjectWorkNatures(); | |||||
preloadStaff(); | preloadStaff(); | ||||
return ( | return ( | ||||
@@ -10,7 +10,7 @@ export interface Customer { | |||||
brNo: string | null; | brNo: string | null; | ||||
address: string | null; | address: string | null; | ||||
district: string | null; | district: string | null; | ||||
customerType: CustomerType | |||||
customerType: CustomerType; | |||||
} | } | ||||
export interface SaveCustomerResponse { | export interface SaveCustomerResponse { | ||||
@@ -53,19 +53,13 @@ export const fetchAllCustomers = cache(async () => { | |||||
}); | }); | ||||
export const fetchSubsidiaries = cache(async () => { | export const fetchSubsidiaries = cache(async () => { | ||||
return serverFetchJson<Subsidiary[]>( | |||||
`${BASE_API_URL}/subsidiary`, | |||||
{ | |||||
next: { tags: ["subsidiary"] }, | |||||
}, | |||||
); | |||||
return serverFetchJson<Subsidiary[]>(`${BASE_API_URL}/subsidiary`, { | |||||
next: { tags: ["subsidiary"] }, | |||||
}); | |||||
}); | }); | ||||
export const fetchCustomerTypes = cache(async () => { | export const fetchCustomerTypes = cache(async () => { | ||||
return serverFetchJson<CustomerType[]>( | |||||
`${BASE_API_URL}/customer/types`, | |||||
{ | |||||
next: { tags: ["customerTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
return serverFetchJson<CustomerType[]>(`${BASE_API_URL}/customer/types`, { | |||||
next: { tags: ["customerTypes"] }, | |||||
}); | |||||
}); |
@@ -3,6 +3,7 @@ | |||||
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 { Task, TaskGroup } from "../tasks"; | import { Task, TaskGroup } from "../tasks"; | ||||
import { Customer } from "../customer"; | |||||
export interface CreateProjectInputs { | export interface CreateProjectInputs { | ||||
// Project details | // Project details | ||||
@@ -12,13 +13,18 @@ export interface CreateProjectInputs { | |||||
projectDescription: string; | projectDescription: string; | ||||
projectLeadId: number; | projectLeadId: number; | ||||
// Project info | |||||
serviceTypeId: number; | |||||
fundingTypeId: number; | |||||
contractTypeId: number; | |||||
locationId: number; | |||||
buildingTypeIds: number[]; | |||||
workNatureIds: number[]; | |||||
// Client details | // Client details | ||||
clientCode: string; | |||||
clientName: string; | |||||
clientContactName: string; | |||||
clientPhone: string; | |||||
clientEmail: string; | |||||
clientSubsidiary: string; | |||||
clientId: Customer["id"]; | |||||
clientContactId: number; | |||||
clientSubsidiaryId?: number; | |||||
// Tasks | // Tasks | ||||
tasks: { | tasks: { | ||||
@@ -50,7 +56,7 @@ export interface CreateProjectInputs { | |||||
}; | }; | ||||
// Miscellaneous | // Miscellaneous | ||||
expectedProjectFee: string; | |||||
expectedProjectFee: number; | |||||
} | } | ||||
export interface ManhourAllocation { | export interface ManhourAllocation { | ||||
@@ -18,6 +18,36 @@ export interface ProjectCategory { | |||||
name: string; | name: string; | ||||
} | } | ||||
export interface ServiceType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface FundingType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface ContractType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface LocationType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface BuildingType { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface WorkNature { | |||||
id: number; | |||||
name: string; | |||||
} | |||||
export interface AssignedProject { | export interface AssignedProject { | ||||
id: number; | id: number; | ||||
code: string; | code: string; | ||||
@@ -44,3 +74,54 @@ export const fetchProjectCategories = cache(async () => { | |||||
}, | }, | ||||
); | ); | ||||
}); | }); | ||||
export const fetchProjectServiceTypes = cache(async () => { | |||||
return serverFetchJson<ServiceType[]>( | |||||
`${BASE_API_URL}/projects/serviceTypes`, | |||||
{ | |||||
next: { tags: ["projectServiceTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchProjectFundingTypes = cache(async () => { | |||||
return serverFetchJson<FundingType[]>( | |||||
`${BASE_API_URL}/projects/fundingTypes`, | |||||
{ | |||||
next: { tags: ["projectFundingTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchProjectContractTypes = cache(async () => { | |||||
return serverFetchJson<ContractType[]>( | |||||
`${BASE_API_URL}/projects/contractTypes`, | |||||
{ | |||||
next: { tags: ["projectContractTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchProjectLocationTypes = cache(async () => { | |||||
return serverFetchJson<LocationType[]>( | |||||
`${BASE_API_URL}/projects/locationTypes`, | |||||
{ | |||||
next: { tags: ["projectLocationTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchProjectBuildingTypes = cache(async () => { | |||||
return serverFetchJson<BuildingType[]>( | |||||
`${BASE_API_URL}/projects/buildingTypes`, | |||||
{ | |||||
next: { tags: ["projectBuildingTypes"] }, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchProjectWorkNatures = cache(async () => { | |||||
return serverFetchJson<WorkNature[]>(`${BASE_API_URL}/projects/workNatures`, { | |||||
next: { tags: ["projectWorkNatures"] }, | |||||
}); | |||||
}); |
@@ -23,16 +23,32 @@ import { | |||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; | import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions"; | ||||
import { Error } from "@mui/icons-material"; | import { Error } from "@mui/icons-material"; | ||||
import { ProjectCategory } from "@/app/api/projects"; | |||||
import { | |||||
BuildingType, | |||||
ContractType, | |||||
FundingType, | |||||
LocationType, | |||||
ProjectCategory, | |||||
ServiceType, | |||||
WorkNature, | |||||
} from "@/app/api/projects"; | |||||
import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import { Grade } from "@/app/api/grades"; | import { Grade } from "@/app/api/grades"; | ||||
import { Customer } from "@/app/api/customer"; | |||||
export interface Props { | export interface Props { | ||||
allTasks: Task[]; | allTasks: Task[]; | ||||
projectCategories: ProjectCategory[]; | projectCategories: ProjectCategory[]; | ||||
taskTemplates: TaskTemplate[]; | taskTemplates: TaskTemplate[]; | ||||
teamLeads: StaffResult[]; | teamLeads: StaffResult[]; | ||||
allCustomers: Customer[]; | |||||
fundingTypes: FundingType[]; | |||||
serviceTypes: ServiceType[]; | |||||
contractTypes: ContractType[]; | |||||
locationTypes: LocationType[]; | |||||
buildingTypes: BuildingType[]; | |||||
workNatures: WorkNature[]; | |||||
// Mocked | // Mocked | ||||
grades: Grade[]; | grades: Grade[]; | ||||
@@ -56,6 +72,13 @@ const CreateProject: React.FC<Props> = ({ | |||||
taskTemplates, | taskTemplates, | ||||
teamLeads, | teamLeads, | ||||
grades, | grades, | ||||
allCustomers, | |||||
contractTypes, | |||||
fundingTypes, | |||||
locationTypes, | |||||
serviceTypes, | |||||
buildingTypes, | |||||
workNatures, | |||||
}) => { | }) => { | ||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
@@ -107,8 +130,6 @@ const CreateProject: React.FC<Props> = ({ | |||||
manhourPercentageByGrade: grades.reduce((acc, grade) => { | manhourPercentageByGrade: grades.reduce((acc, grade) => { | ||||
return { ...acc, [grade.id]: 1 / grades.length }; | return { ...acc, [grade.id]: 1 / grades.length }; | ||||
}, {}), | }, {}), | ||||
// TODO: Remove this | |||||
clientSubsidiary: "Test subsidiary", | |||||
}, | }, | ||||
}); | }); | ||||
@@ -137,6 +158,13 @@ const CreateProject: React.FC<Props> = ({ | |||||
</Tabs> | </Tabs> | ||||
{ | { | ||||
<ProjectClientDetails | <ProjectClientDetails | ||||
buildingTypes={buildingTypes} | |||||
workNatures={workNatures} | |||||
contractTypes={contractTypes} | |||||
fundingTypes={fundingTypes} | |||||
locationTypes={locationTypes} | |||||
serviceTypes={serviceTypes} | |||||
allCustomers={allCustomers} | |||||
projectCategories={projectCategories} | projectCategories={projectCategories} | ||||
teamLeads={teamLeads} | teamLeads={teamLeads} | ||||
isActive={tabIndex === 0} | isActive={tabIndex === 0} | ||||
@@ -1,16 +1,43 @@ | |||||
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | ||||
import CreateProject from "./CreateProject"; | import CreateProject from "./CreateProject"; | ||||
import { fetchProjectCategories } from "@/app/api/projects"; | |||||
import { | |||||
fetchProjectBuildingTypes, | |||||
fetchProjectCategories, | |||||
fetchProjectContractTypes, | |||||
fetchProjectFundingTypes, | |||||
fetchProjectLocationTypes, | |||||
fetchProjectServiceTypes, | |||||
fetchProjectWorkNatures, | |||||
} from "@/app/api/projects"; | |||||
import { fetchTeamLeads } from "@/app/api/staff"; | import { fetchTeamLeads } from "@/app/api/staff"; | ||||
import { fetchAllCustomers } from "@/app/api/customer"; | |||||
const CreateProjectWrapper: React.FC = async () => { | const CreateProjectWrapper: React.FC = async () => { | ||||
const [tasks, taskTemplates, projectCategories, teamLeads] = | |||||
await Promise.all([ | |||||
fetchAllTasks(), | |||||
fetchTaskTemplates(), | |||||
fetchProjectCategories(), | |||||
fetchTeamLeads(), | |||||
]); | |||||
const [ | |||||
tasks, | |||||
taskTemplates, | |||||
projectCategories, | |||||
teamLeads, | |||||
allCustomers, | |||||
contractTypes, | |||||
fundingTypes, | |||||
locationTypes, | |||||
serviceTypes, | |||||
buildingTypes, | |||||
workNatures, | |||||
] = await Promise.all([ | |||||
fetchAllTasks(), | |||||
fetchTaskTemplates(), | |||||
fetchProjectCategories(), | |||||
fetchTeamLeads(), | |||||
fetchAllCustomers(), | |||||
fetchProjectContractTypes(), | |||||
fetchProjectFundingTypes(), | |||||
fetchProjectLocationTypes(), | |||||
fetchProjectServiceTypes(), | |||||
fetchProjectBuildingTypes(), | |||||
fetchProjectWorkNatures(), | |||||
]); | |||||
return ( | return ( | ||||
<CreateProject | <CreateProject | ||||
@@ -18,6 +45,13 @@ const CreateProjectWrapper: React.FC = async () => { | |||||
projectCategories={projectCategories} | projectCategories={projectCategories} | ||||
taskTemplates={taskTemplates} | taskTemplates={taskTemplates} | ||||
teamLeads={teamLeads} | teamLeads={teamLeads} | ||||
allCustomers={allCustomers} | |||||
contractTypes={contractTypes} | |||||
fundingTypes={fundingTypes} | |||||
locationTypes={locationTypes} | |||||
serviceTypes={serviceTypes} | |||||
buildingTypes={buildingTypes} | |||||
workNatures={workNatures} | |||||
// Mocks | // Mocks | ||||
grades={[ | grades={[ | ||||
{ name: "Grade 1", id: 1, code: "1" }, | { name: "Grade 1", id: 1, code: "1" }, | ||||
@@ -17,27 +17,94 @@ import RestartAlt from "@mui/icons-material/RestartAlt"; | |||||
import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
import { Controller, useFormContext } from "react-hook-form"; | import { Controller, useFormContext } from "react-hook-form"; | ||||
import { CreateProjectInputs } from "@/app/api/projects/actions"; | import { CreateProjectInputs } from "@/app/api/projects/actions"; | ||||
import { ProjectCategory } from "@/app/api/projects"; | |||||
import { | |||||
BuildingType, | |||||
ContractType, | |||||
FundingType, | |||||
LocationType, | |||||
ProjectCategory, | |||||
ServiceType, | |||||
WorkNature, | |||||
} from "@/app/api/projects"; | |||||
import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
import { Contact, Customer } from "@/app/api/customer"; | |||||
import Link from "next/link"; | |||||
import React, { useEffect, useMemo, useState } from "react"; | |||||
import { fetchCustomer } from "@/app/api/customer/actions"; | |||||
import { Checkbox, ListItemText } from "@mui/material"; | |||||
interface Props { | interface Props { | ||||
isActive: boolean; | isActive: boolean; | ||||
projectCategories: ProjectCategory[]; | projectCategories: ProjectCategory[]; | ||||
teamLeads: StaffResult[]; | teamLeads: StaffResult[]; | ||||
allCustomers: Customer[]; | |||||
serviceTypes: ServiceType[]; | |||||
contractTypes: ContractType[]; | |||||
fundingTypes: FundingType[]; | |||||
locationTypes: LocationType[]; | |||||
buildingTypes: BuildingType[]; | |||||
workNatures: WorkNature[]; | |||||
} | } | ||||
const ProjectClientDetails: React.FC<Props> = ({ | const ProjectClientDetails: React.FC<Props> = ({ | ||||
isActive, | isActive, | ||||
projectCategories, | projectCategories, | ||||
teamLeads | |||||
teamLeads, | |||||
allCustomers, | |||||
serviceTypes, | |||||
contractTypes, | |||||
fundingTypes, | |||||
locationTypes, | |||||
buildingTypes, | |||||
workNatures, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const { | const { | ||||
register, | register, | ||||
formState: { errors }, | formState: { errors }, | ||||
watch, | |||||
control, | control, | ||||
} = useFormContext<CreateProjectInputs>(); | } = useFormContext<CreateProjectInputs>(); | ||||
const selectedCustomerId = watch("clientId"); | |||||
const selectedCustomer = useMemo( | |||||
() => allCustomers.find((c) => c.id === selectedCustomerId), | |||||
[allCustomers, selectedCustomerId], | |||||
); | |||||
const [customerContacts, setCustomerContacts] = useState<Contact[]>([]); | |||||
const [customerSubsidiaryIds, setCustomerSubsidiaryIds] = useState<number[]>( | |||||
[], | |||||
); | |||||
const selectedCustomerContactId = watch("clientContactId"); | |||||
const selectedCustomerContact = useMemo( | |||||
() => | |||||
customerContacts.find( | |||||
(contact) => contact.id === selectedCustomerContactId, | |||||
), | |||||
[customerContacts, selectedCustomerContactId], | |||||
); | |||||
useEffect(() => { | |||||
if (selectedCustomerId !== undefined) { | |||||
fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => { | |||||
setCustomerContacts(contacts); | |||||
setCustomerSubsidiaryIds(subsidiaryIds); | |||||
}); | |||||
} | |||||
}, [selectedCustomerId]); | |||||
const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>( | |||||
(acc, building) => ({ ...acc, [building.id]: building.name }), | |||||
{}, | |||||
); | |||||
const workNatureIdNameMap = workNatures.reduce<{ [id: number]: string }>( | |||||
(acc, wn) => ({ ...acc, [wn.id]: wn.name }), | |||||
{}, | |||||
); | |||||
return ( | return ( | ||||
<Card sx={{ display: isActive ? "block" : "none" }}> | <Card sx={{ display: isActive ? "block" : "none" }}> | ||||
<CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
@@ -107,6 +174,146 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Service Type")}</InputLabel> | |||||
<Controller | |||||
defaultValue={serviceTypes[0].id} | |||||
control={control} | |||||
name="serviceTypeId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Service Type")} {...field}> | |||||
{serviceTypes.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
{type.name} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Funding Type")}</InputLabel> | |||||
<Controller | |||||
defaultValue={fundingTypes[0].id} | |||||
control={control} | |||||
name="fundingTypeId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Funding Type")} {...field}> | |||||
{fundingTypes.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
{type.name} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Contract Type")}</InputLabel> | |||||
<Controller | |||||
defaultValue={contractTypes[0].id} | |||||
control={control} | |||||
name="contractTypeId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Contract Type")} {...field}> | |||||
{contractTypes.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
{type.name} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Location")}</InputLabel> | |||||
<Controller | |||||
defaultValue={locationTypes[0].id} | |||||
control={control} | |||||
name="locationId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Location")} {...field}> | |||||
{locationTypes.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
{type.name} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Building Types")}</InputLabel> | |||||
<Controller | |||||
defaultValue={[]} | |||||
control={control} | |||||
name="buildingTypeIds" | |||||
render={({ field }) => ( | |||||
<Select | |||||
renderValue={(types) => | |||||
types | |||||
.map((type) => buildingTypeIdNameMap[type]) | |||||
.join(", ") | |||||
} | |||||
multiple | |||||
label={t("Building Types")} | |||||
{...field} | |||||
> | |||||
{buildingTypes.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
<Checkbox | |||||
checked={field.value.indexOf(type.id) > -1} | |||||
/> | |||||
<ListItemText primary={type.name} /> | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Work Nature")}</InputLabel> | |||||
<Controller | |||||
defaultValue={[]} | |||||
control={control} | |||||
name="workNatureIds" | |||||
render={({ field }) => ( | |||||
<Select | |||||
renderValue={(types) => | |||||
types | |||||
.map((type) => workNatureIdNameMap[type]) | |||||
.join(", ") | |||||
} | |||||
multiple | |||||
label={t("Work Nature")} | |||||
{...field} | |||||
> | |||||
{workNatures.map((type, index) => ( | |||||
<MenuItem key={`${type.id}-${index}`} value={type.id}> | |||||
<Checkbox | |||||
checked={field.value.indexOf(type.id) > -1} | |||||
/> | |||||
<ListItemText primary={type.name} /> | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("Project Description")} | label={t("Project Description")} | ||||
@@ -122,65 +329,160 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
label={t("Expected Total Project Fee")} | label={t("Expected Total Project Fee")} | ||||
fullWidth | fullWidth | ||||
type="number" | type="number" | ||||
{...register("expectedProjectFee")} | |||||
{...register("expectedProjectFee", { valueAsNumber: true })} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
</Box> | </Box> | ||||
<Box> | <Box> | ||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | |||||
{t("Client Details")} | |||||
</Typography> | |||||
<Stack | |||||
direction="row" | |||||
alignItems="center" | |||||
marginBlockEnd={1} | |||||
spacing={2} | |||||
> | |||||
<Typography variant="overline" display="block"> | |||||
{t("Client Details")} | |||||
</Typography> | |||||
<Button LinkComponent={Link} href="/settings/customer"> | |||||
{t("Add or Edit Clients")} | |||||
</Button> | |||||
</Stack> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | |||||
label={t("Client Code")} | |||||
fullWidth | |||||
{...register("clientCode")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Client Name")} | |||||
fullWidth | |||||
{...register("clientName")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Client Lead Name")} | |||||
fullWidth | |||||
{...register("clientContactName")} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Client Lead Phone Number")} | |||||
fullWidth | |||||
{...register("clientPhone")} | |||||
/> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Client")}</InputLabel> | |||||
<Controller | |||||
defaultValue={allCustomers[0].id} | |||||
control={control} | |||||
name="clientId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Client")} {...field}> | |||||
{allCustomers.map((customer, index) => ( | |||||
<MenuItem | |||||
key={`${customer.id}-${index}`} | |||||
value={customer.id} | |||||
> | |||||
{`${customer.code} - ${customer.name}`} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | </Grid> | ||||
<Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | |||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("Client Lead Email")} | |||||
label={t("Client Type")} | |||||
InputProps={{ | |||||
readOnly: true, | |||||
}} | |||||
fullWidth | fullWidth | ||||
{...register("clientEmail")} | |||||
value={selectedCustomer?.customerType.name || ""} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<FormControl fullWidth> | |||||
<InputLabel>{t("Client Subsidiary")}</InputLabel> | |||||
<Select | |||||
label={t("Client Subsidiary")} | |||||
value={"Test Subsidiary"} | |||||
<Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | |||||
{customerContacts.length > 0 && ( | |||||
<> | |||||
<Grid item xs={6}> | |||||
<FormControl | |||||
fullWidth | |||||
error={Boolean(errors.clientContactId)} | |||||
> | |||||
<InputLabel>{t("Client Lead")}</InputLabel> | |||||
<Controller | |||||
rules={{ | |||||
validate: (value) => { | |||||
if ( | |||||
!customerContacts.find( | |||||
(contact) => contact.id === value, | |||||
) | |||||
) { | |||||
return t("Please provide a valid contact"); | |||||
} else return true; | |||||
}, | |||||
}} | |||||
defaultValue={customerContacts[0].id} | |||||
control={control} | |||||
name="clientContactId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Client Lead")} {...field}> | |||||
{customerContacts.map((contact, index) => ( | |||||
<MenuItem | |||||
key={`${contact.id}-${index}`} | |||||
value={contact.id} | |||||
> | |||||
{contact.name} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
<Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Client Lead Phone Number")} | |||||
fullWidth | |||||
InputProps={{ | |||||
readOnly: true, | |||||
}} | |||||
value={selectedCustomerContact?.phone || ""} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Client Lead Email")} | |||||
fullWidth | |||||
InputProps={{ | |||||
readOnly: true, | |||||
}} | |||||
value={selectedCustomerContact?.email || ""} | |||||
/> | |||||
</Grid> | |||||
</> | |||||
)} | |||||
{customerSubsidiaryIds.length > 0 && ( | |||||
<Grid item xs={6}> | |||||
<FormControl | |||||
fullWidth | |||||
error={Boolean(errors.clientSubsidiaryId)} | |||||
> | > | ||||
<MenuItem value={"Test Subsidiary"}> | |||||
{t("Test Subsidiary")} | |||||
</MenuItem> | |||||
</Select> | |||||
</FormControl> | |||||
</Grid> | |||||
<InputLabel>{t("Client Subsidiary")}</InputLabel> | |||||
<Controller | |||||
rules={{ | |||||
validate: (value) => { | |||||
if ( | |||||
!customerSubsidiaryIds.find( | |||||
(subsidiaryId) => subsidiaryId === value, | |||||
) | |||||
) { | |||||
return t("Please choose a valid subsidiary"); | |||||
} else return true; | |||||
}, | |||||
}} | |||||
defaultValue={customerSubsidiaryIds[0]} | |||||
control={control} | |||||
name="clientSubsidiaryId" | |||||
render={({ field }) => ( | |||||
<Select label={t("Client Lead")} {...field}> | |||||
{customerSubsidiaryIds.map((subsidiaryId, index) => ( | |||||
<MenuItem | |||||
key={`${subsidiaryId}-${index}`} | |||||
value={subsidiaryId} | |||||
> | |||||
{subsidiaryId} | |||||
</MenuItem> | |||||
))} | |||||
</Select> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
)} | |||||
</Grid> | </Grid> | ||||
</Box> | </Box> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
@@ -14,7 +14,7 @@ const ProjectTotalFee: React.FC<Props> = ({ taskGroups }) => { | |||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const { watch } = useFormContext<CreateProjectInputs>(); | const { watch } = useFormContext<CreateProjectInputs>(); | ||||
const milestones = watch("milestones"); | const milestones = watch("milestones"); | ||||
const expectedTotalFee = Number(watch("expectedProjectFee")); | |||||
const expectedTotalFee = watch("expectedProjectFee"); | |||||
let projectTotal = 0; | let projectTotal = 0; | ||||