| @@ -8,7 +8,7 @@ export default async function NotFound() { | |||||
| return ( | return ( | ||||
| <Stack spacing={2}> | <Stack spacing={2}> | ||||
| <Typography variant="h4">{t("Not Found")}</Typography> | <Typography variant="h4">{t("Not Found")}</Typography> | ||||
| <Typography variant="body1">{t("The sub project was not found or there was no any main projects!")}</Typography> | |||||
| <Typography variant="body1">{t("There was no any main projects!")}</Typography> | |||||
| <Link href="/projects" component={NextLink} variant="body2"> | <Link href="/projects" component={NextLink} variant="body2"> | ||||
| {t("Return to all projects")} | {t("Return to all projects")} | ||||
| </Link> | </Link> | ||||
| @@ -21,7 +21,7 @@ import { Metadata } from "next"; | |||||
| import { notFound } from "next/navigation"; | import { notFound } from "next/navigation"; | ||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Create Project", | |||||
| title: "Create Sub Project", | |||||
| }; | }; | ||||
| const Projects: React.FC = async () => { | const Projects: React.FC = async () => { | ||||
| @@ -0,0 +1,17 @@ | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import { Stack, Typography, Link } from "@mui/material"; | |||||
| import NextLink from "next/link"; | |||||
| export default async function NotFound() { | |||||
| const { t } = await getServerI18n("projects", "common"); | |||||
| return ( | |||||
| <Stack spacing={2}> | |||||
| <Typography variant="h4">{t("Not Found")}</Typography> | |||||
| <Typography variant="body1">{t("The sub project was not found!")}</Typography> | |||||
| <Link href="/projects" component={NextLink} variant="body2"> | |||||
| {t("Return to all projects")} | |||||
| </Link> | |||||
| </Stack> | |||||
| ); | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer"; | |||||
| import { fetchGrades } from "@/app/api/grades"; | |||||
| import { | |||||
| fetchMainProjects, | |||||
| fetchProjectBuildingTypes, | |||||
| fetchProjectCategories, | |||||
| fetchProjectContractTypes, | |||||
| fetchProjectDetails, | |||||
| fetchProjectFundingTypes, | |||||
| fetchProjectLocationTypes, | |||||
| fetchProjectServiceTypes, | |||||
| fetchProjectWorkNatures, | |||||
| } from "@/app/api/projects"; | |||||
| import { preloadStaff, preloadTeamLeads } from "@/app/api/staff"; | |||||
| import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | |||||
| import CreateProject from "@/components/CreateProject"; | |||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
| import Typography from "@mui/material/Typography"; | |||||
| import { isArray } from "lodash"; | |||||
| import { Metadata } from "next"; | |||||
| import { notFound } from "next/navigation"; | |||||
| interface Props { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| export const metadata: Metadata = { | |||||
| title: "Edit Sub Project", | |||||
| }; | |||||
| const Projects: React.FC<Props> = async ({ searchParams }) => { | |||||
| const { t } = await getServerI18n("projects"); | |||||
| const projectId = searchParams["id"]; | |||||
| if (!projectId || isArray(projectId)) { | |||||
| notFound(); | |||||
| } | |||||
| // Preload necessary dependencies | |||||
| fetchAllTasks(); | |||||
| fetchTaskTemplates(); | |||||
| fetchProjectCategories(); | |||||
| fetchProjectContractTypes(); | |||||
| fetchProjectFundingTypes(); | |||||
| fetchProjectLocationTypes(); | |||||
| fetchProjectServiceTypes(); | |||||
| fetchProjectBuildingTypes(); | |||||
| fetchProjectWorkNatures(); | |||||
| fetchAllCustomers(); | |||||
| fetchAllSubsidiaries(); | |||||
| fetchGrades(); | |||||
| preloadTeamLeads(); | |||||
| preloadStaff(); | |||||
| try { | |||||
| await fetchProjectDetails(projectId); | |||||
| const data = await fetchMainProjects(); | |||||
| if (!Boolean(data) || data.length === 0) { | |||||
| notFound(); | |||||
| } | |||||
| } catch (e) { | |||||
| notFound(); | |||||
| } | |||||
| return ( | |||||
| <> | |||||
| <Typography variant="h4">{t("Edit Sub Project")}</Typography> | |||||
| <I18nProvider namespaces={["projects"]}> | |||||
| <CreateProject isEditMode isSubProject projectId={projectId}/> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default Projects; | |||||
| @@ -36,8 +36,8 @@ export interface CreateProjectInputs { | |||||
| // Client details | // Client details | ||||
| clientId: Customer["id"]; | clientId: Customer["id"]; | ||||
| clientContactId?: number; | clientContactId?: number; | ||||
| clientSubsidiaryId?: number; | |||||
| subsidiaryContactId: number; | |||||
| clientSubsidiaryId?: number | null; | |||||
| subsidiaryContactId?: number; | |||||
| isSubsidiaryContact?: boolean; | isSubsidiaryContact?: boolean; | ||||
| // Allocation | // Allocation | ||||
| @@ -13,6 +13,7 @@ export interface ProjectResult { | |||||
| team: string; | team: string; | ||||
| client: string; | client: string; | ||||
| status: string; | status: string; | ||||
| mainProject: string; | |||||
| } | } | ||||
| export interface MainProject { | export interface MainProject { | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use client" | "use client" | ||||
| import { Autocomplete, MenuItem, TextField, Checkbox } from "@mui/material"; | |||||
| import { Autocomplete, MenuItem, TextField, Checkbox, Chip } from "@mui/material"; | |||||
| import { Controller, FieldValues, Path, Control, RegisterOptions } from "react-hook-form"; | import { Controller, FieldValues, Path, Control, RegisterOptions } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; | import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; | ||||
| @@ -18,6 +18,7 @@ interface Props<T extends { id?: number | string | null; label?: string; name?: | |||||
| noOptionsText?: string, | noOptionsText?: string, | ||||
| isMultiple?: boolean, | isMultiple?: boolean, | ||||
| rules?: RegisterOptions<FieldValues> | rules?: RegisterOptions<FieldValues> | ||||
| disabled?: boolean, | |||||
| } | } | ||||
| function ControlledAutoComplete< | function ControlledAutoComplete< | ||||
| @@ -27,11 +28,10 @@ function ControlledAutoComplete< | |||||
| props: Props<T, TField> | props: Props<T, TField> | ||||
| ) { | ) { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { control, options, name, label, noOptionsText, isMultiple, rules } = props; | |||||
| const { control, options, name, label, noOptionsText, isMultiple, rules, disabled } = props; | |||||
| // set default value if value is null | // set default value if value is null | ||||
| if (!Boolean(isMultiple) && !Boolean(control._formValues[name])) { | if (!Boolean(isMultiple) && !Boolean(control._formValues[name])) { | ||||
| console.log(name, control._formValues[name]) | |||||
| control._formValues[name] = options[0]?.id ?? undefined | control._formValues[name] = options[0]?.id ?? undefined | ||||
| } else if (Boolean(isMultiple) && !Boolean(control._formValues[name])) { | } else if (Boolean(isMultiple) && !Boolean(control._formValues[name])) { | ||||
| control._formValues[name] = [] | control._formValues[name] = [] | ||||
| @@ -42,7 +42,6 @@ function ControlledAutoComplete< | |||||
| name={name} | name={name} | ||||
| control={control} | control={control} | ||||
| rules={rules} | rules={rules} | ||||
| render={({ field, fieldState, formState }) => { | render={({ field, fieldState, formState }) => { | ||||
| return ( | return ( | ||||
| @@ -51,7 +50,8 @@ function ControlledAutoComplete< | |||||
| multiple | multiple | ||||
| disableClearable | disableClearable | ||||
| disableCloseOnSelect | disableCloseOnSelect | ||||
| disablePortal | |||||
| // disablePortal | |||||
| disabled={disabled} | |||||
| noOptionsText={noOptionsText ?? t("No Options")} | noOptionsText={noOptionsText ?? t("No Options")} | ||||
| value={options.filter(option => { | value={options.filter(option => { | ||||
| return field.value?.includes(option.id) | return field.value?.includes(option.id) | ||||
| @@ -61,7 +61,7 @@ function ControlledAutoComplete< | |||||
| isOptionEqualToValue={(option, value) => option.id === value.id} | isOptionEqualToValue={(option, value) => option.id === value.id} | ||||
| renderOption={(params, option, { selected }) => { | renderOption={(params, option, { selected }) => { | ||||
| return ( | return ( | ||||
| <li {...params} key={option.id}> | |||||
| <li {...params} key={option?.id}> | |||||
| <Checkbox | <Checkbox | ||||
| icon={icon} | icon={icon} | ||||
| checkedIcon={checkedIcon} | checkedIcon={checkedIcon} | ||||
| @@ -72,6 +72,11 @@ function ControlledAutoComplete< | |||||
| </li> | </li> | ||||
| ); | ); | ||||
| }} | }} | ||||
| // renderTags={(tagValue, getTagProps) => { | |||||
| // return tagValue.map((option, index) => ( | |||||
| // <Chip {...getTagProps({ index })} key={option?.id} label={option.label ?? option.name} /> | |||||
| // )) | |||||
| // }} | |||||
| onChange={(event, value) => { | onChange={(event, value) => { | ||||
| field.onChange(value?.map(v => v.id)) | field.onChange(value?.map(v => v.id)) | ||||
| }} | }} | ||||
| @@ -80,7 +85,8 @@ function ControlledAutoComplete< | |||||
| : | : | ||||
| <Autocomplete | <Autocomplete | ||||
| disableClearable | disableClearable | ||||
| disablePortal | |||||
| // disablePortal | |||||
| disabled={disabled} | |||||
| noOptionsText={noOptionsText ?? t("No Options")} | noOptionsText={noOptionsText ?? t("No Options")} | ||||
| value={options.find(option => option.id === field.value) ?? options[0]} | value={options.find(option => option.id === field.value) ?? options[0]} | ||||
| options={options} | options={options} | ||||
| @@ -88,13 +94,18 @@ function ControlledAutoComplete< | |||||
| isOptionEqualToValue={(option, value) => option?.id === value?.id} | isOptionEqualToValue={(option, value) => option?.id === value?.id} | ||||
| renderOption={(params, option) => { | renderOption={(params, option) => { | ||||
| return ( | return ( | ||||
| <MenuItem {...params} key={option.id} value={option.id}> | |||||
| <MenuItem {...params} key={option?.id} value={option.id}> | |||||
| {option.label ?? option.name} | {option.label ?? option.name} | ||||
| </MenuItem> | </MenuItem> | ||||
| ); | ); | ||||
| }} | }} | ||||
| // renderTags={(tagValue, getTagProps) => { | |||||
| // return tagValue.map((option, index) => ( | |||||
| // <Chip {...getTagProps({ index })} key={option?.id} label={option.label ?? option.name} /> | |||||
| // )) | |||||
| // }} | |||||
| onChange={(event, value) => { | onChange={(event, value) => { | ||||
| field.onChange(value?.id) | |||||
| field.onChange(value?.id ?? null) | |||||
| }} | }} | ||||
| renderInput={(params) => <TextField {...params} error={Boolean(formState.errors[name])} variant="outlined" label={label} />} | renderInput={(params) => <TextField {...params} error={Boolean(formState.errors[name])} variant="outlined" label={label} />} | ||||
| />) | />) | ||||
| @@ -393,6 +393,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| projectCategories={projectCategories} | projectCategories={projectCategories} | ||||
| teamLeads={teamLeads} | teamLeads={teamLeads} | ||||
| isActive={tabIndex === 0} | isActive={tabIndex === 0} | ||||
| isEditMode={isEditMode} | |||||
| /> | /> | ||||
| } | } | ||||
| { | { | ||||
| @@ -4,16 +4,18 @@ import Card from "@mui/material/Card"; | |||||
| import CardContent from "@mui/material/CardContent"; | import CardContent from "@mui/material/CardContent"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import React, { SyntheticEvent, useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import CardActions from "@mui/material/CardActions"; | import CardActions from "@mui/material/CardActions"; | ||||
| import RestartAlt from "@mui/icons-material/RestartAlt"; | import RestartAlt from "@mui/icons-material/RestartAlt"; | ||||
| import { | import { | ||||
| Alert, | Alert, | ||||
| Autocomplete, | |||||
| FormControl, | FormControl, | ||||
| InputLabel, | InputLabel, | ||||
| MenuItem, | MenuItem, | ||||
| Select, | Select, | ||||
| SelectChangeEvent, | SelectChangeEvent, | ||||
| TextField, | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { Task, TaskGroup } from "@/app/api/tasks"; | import { Task, TaskGroup } from "@/app/api/tasks"; | ||||
| import uniqBy from "lodash/uniqBy"; | import uniqBy from "lodash/uniqBy"; | ||||
| @@ -49,8 +51,8 @@ const Milestone: React.FC<Props> = ({ allTasks, isActive }) => { | |||||
| taskGroups[0].id, | taskGroups[0].id, | ||||
| ); | ); | ||||
| const onSelectTaskGroup = useCallback( | const onSelectTaskGroup = useCallback( | ||||
| (event: SelectChangeEvent<TaskGroup["id"]>) => { | |||||
| const id = event.target.value; | |||||
| (event: SyntheticEvent<Element, Event>, value: NonNullable<TaskGroup>) => { | |||||
| const id = value.id; | |||||
| const newTaksGroupId = typeof id === "string" ? parseInt(id) : id; | const newTaksGroupId = typeof id === "string" ? parseInt(id) : id; | ||||
| setCurrentTaskGroupId(newTaksGroupId); | setCurrentTaskGroupId(newTaksGroupId); | ||||
| }, | }, | ||||
| @@ -81,7 +83,7 @@ const Milestone: React.FC<Props> = ({ allTasks, isActive }) => { | |||||
| } | } | ||||
| // console.log(Object.keys(milestones).reduce((acc, key) => acc + milestones[parseFloat(key)].payments.reduce((acc2, value) => acc2 + value.amount, 0), 0)) | // console.log(Object.keys(milestones).reduce((acc, key) => acc + milestones[parseFloat(key)].payments.reduce((acc2, value) => acc2 + value.amount, 0), 0)) | ||||
| if (hasError) { | if (hasError) { | ||||
| setError("milestones", {message: "milestones is not valid", type: "invalid"}) | |||||
| setError("milestones", { message: "milestones is not valid", type: "invalid" }) | |||||
| } else { | } else { | ||||
| clearErrors("milestones") | clearErrors("milestones") | ||||
| } | } | ||||
| @@ -92,26 +94,32 @@ const Milestone: React.FC<Props> = ({ allTasks, isActive }) => { | |||||
| <Card sx={{ display: isActive ? "block" : "none" }}> | <Card sx={{ display: isActive ? "block" : "none" }}> | ||||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 2 }}> | <CardContent sx={{ display: "flex", flexDirection: "column", gap: 2 }}> | ||||
| <FormControl> | <FormControl> | ||||
| <InputLabel>{t("Task Stage")}</InputLabel> | |||||
| <Select | |||||
| label={t("Task Stage")} | |||||
| <Autocomplete | |||||
| disableClearable | |||||
| // disablePortal | |||||
| noOptionsText={t("No Task Stage")} | |||||
| value={taskGroups.find(taskGroup => taskGroup.id === currentTaskGroupId)} | |||||
| options={taskGroups} | |||||
| getOptionLabel={(taskGroup) => taskGroup.name} | |||||
| isOptionEqualToValue={(option, value) => option.id === value.id} | |||||
| renderOption={(params, option) => { | |||||
| return ( | |||||
| <MenuItem {...params} key={option.id} value={option.id}> | |||||
| {option.name} | |||||
| </MenuItem> | |||||
| ); | |||||
| }} | |||||
| onChange={onSelectTaskGroup} | onChange={onSelectTaskGroup} | ||||
| value={currentTaskGroupId} | |||||
| > | |||||
| {taskGroups.map((taskGroup) => ( | |||||
| <MenuItem key={taskGroup.id} value={taskGroup.id}> | |||||
| {taskGroup.name} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| renderInput={(params) => <TextField {...params} variant="outlined" label={t("Task Stage")} />} | |||||
| /> | |||||
| </FormControl> | </FormControl> | ||||
| {/* MUI X-Grid will throw an error if it is rendered without any dimensions; so not rendering when not active */} | {/* MUI X-Grid will throw an error if it is rendered without any dimensions; so not rendering when not active */} | ||||
| {isActive && <MilestoneSection taskGroupId={currentTaskGroupId} />} | {isActive && <MilestoneSection taskGroupId={currentTaskGroupId} />} | ||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| {/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| <Button variant="text" startIcon={<RestartAlt />}> | <Button variant="text" startIcon={<RestartAlt />}> | ||||
| {t("Reset")} | {t("Reset")} | ||||
| </Button> | </Button> | ||||
| </CardActions> | |||||
| </CardActions> */} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| <Card sx={{ display: isActive ? "block" : "none" }}> | <Card sx={{ display: isActive ? "block" : "none" }}> | ||||
| @@ -39,6 +39,7 @@ import ControlledAutoComplete from "../ControlledAutoComplete/ControlledAutoComp | |||||
| interface Props { | interface Props { | ||||
| isActive: boolean; | isActive: boolean; | ||||
| isSubProject: boolean; | isSubProject: boolean; | ||||
| isEditMode: boolean; | |||||
| mainProjects?: MainProject[]; | mainProjects?: MainProject[]; | ||||
| projectCategories: ProjectCategory[]; | projectCategories: ProjectCategory[]; | ||||
| teamLeads: StaffResult[]; | teamLeads: StaffResult[]; | ||||
| @@ -55,6 +56,7 @@ interface Props { | |||||
| const ProjectClientDetails: React.FC<Props> = ({ | const ProjectClientDetails: React.FC<Props> = ({ | ||||
| isActive, | isActive, | ||||
| isSubProject, | isSubProject, | ||||
| isEditMode, | |||||
| mainProjects, | mainProjects, | ||||
| projectCategories, | projectCategories, | ||||
| teamLeads, | teamLeads, | ||||
| @@ -110,6 +112,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| ); | ); | ||||
| // get customer (client) contact combo | // get customer (client) contact combo | ||||
| const [firstCustomerLoaded, setFirstCustomerLoaded] = useState(false) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (selectedCustomerId !== undefined) { | if (selectedCustomerId !== undefined) { | ||||
| fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => { | fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => { | ||||
| @@ -118,7 +121,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| // if (subsidiaryIds.length > 0) setValue("clientSubsidiaryId", subsidiaryIds[0]) | // if (subsidiaryIds.length > 0) setValue("clientSubsidiaryId", subsidiaryIds[0]) | ||||
| // else | // else | ||||
| setValue("clientSubsidiaryId", undefined) | |||||
| if (isEditMode && !firstCustomerLoaded) { setFirstCustomerLoaded(true) } else setValue("clientSubsidiaryId", null) | |||||
| // if (contacts.length > 0) setValue("clientContactId", contacts[0].id) | // if (contacts.length > 0) setValue("clientContactId", contacts[0].id) | ||||
| // else setValue("clientContactId", undefined) | // else setValue("clientContactId", undefined) | ||||
| }); | }); | ||||
| @@ -130,11 +133,11 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| if (Boolean(clientSubsidiaryId)) { | if (Boolean(clientSubsidiaryId)) { | ||||
| // get subsidiary contact combo | // get subsidiary contact combo | ||||
| const contacts = allSubsidiaries.find(subsidiary => subsidiary.id === clientSubsidiaryId)?.subsidiaryContacts!! | const contacts = allSubsidiaries.find(subsidiary => subsidiary.id === clientSubsidiaryId)?.subsidiaryContacts!! | ||||
| setSubsidiaryContacts(contacts) | |||||
| setSubsidiaryContacts(() => contacts) | |||||
| setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && Boolean(defaultValues?.clientSubsidiaryId) ? contacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? contacts[0].id : contacts[0].id) | setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && Boolean(defaultValues?.clientSubsidiaryId) ? contacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? contacts[0].id : contacts[0].id) | ||||
| setValue("isSubsidiaryContact", true) | setValue("isSubsidiaryContact", true) | ||||
| } else if (customerContacts?.length > 0) { | } else if (customerContacts?.length > 0) { | ||||
| setSubsidiaryContacts([]) | |||||
| setSubsidiaryContacts(() => []) | |||||
| setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && !Boolean(defaultValues?.clientSubsidiaryId) ? customerContacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? customerContacts[0].id : customerContacts[0].id) | setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && !Boolean(defaultValues?.clientSubsidiaryId) ? customerContacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? customerContacts[0].id : customerContacts[0].id) | ||||
| setValue("isSubsidiaryContact", false) | setValue("isSubsidiaryContact", false) | ||||
| } | } | ||||
| @@ -153,7 +156,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| // Automatically update the project & client details whene select a main project | // Automatically update the project & client details whene select a main project | ||||
| const mainProjectId = watch("mainProjectId") | const mainProjectId = watch("mainProjectId") | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (mainProjectId !== undefined && mainProjects !== undefined) { | |||||
| if (mainProjectId !== undefined && mainProjects !== undefined && !isEditMode) { | |||||
| const mainProject = mainProjects.find(project => project.projectId === mainProjectId); | const mainProject = mainProjects.find(project => project.projectId === mainProjectId); | ||||
| if (mainProject !== undefined) { | if (mainProject !== undefined) { | ||||
| @@ -174,7 +177,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| setValue("clientContactId", mainProject.clientContactId) | setValue("clientContactId", mainProject.clientContactId) | ||||
| } | } | ||||
| } | } | ||||
| }, [getValues, mainProjectId, setValue]) | |||||
| }, [getValues, mainProjectId, setValue, isEditMode]) | |||||
| // const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>( | // const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>( | ||||
| // (acc, building) => ({ ...acc, [building.id]: building.name }), | // (acc, building) => ({ ...acc, [building.id]: building.name }), | ||||
| @@ -202,6 +205,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| name="mainProjectId" | name="mainProjectId" | ||||
| label={t("Main Project")} | label={t("Main Project")} | ||||
| noOptionsText={t("No Main Project")} | noOptionsText={t("No Main Project")} | ||||
| disabled={isEditMode} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item sx={{ display: { xs: "none", sm: "block" } }} /></> | <Grid item sx={{ display: { xs: "none", sm: "block" } }} /></> | ||||
| @@ -438,11 +442,11 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| )} | )} | ||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| {/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| <Button variant="text" startIcon={<RestartAlt />}> | <Button variant="text" startIcon={<RestartAlt />}> | ||||
| {t("Reset")} | {t("Reset")} | ||||
| </Button> | </Button> | ||||
| </CardActions> | |||||
| </CardActions> */} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| ); | ); | ||||
| @@ -1,7 +1,7 @@ | |||||
| "use client"; | "use client"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import React, { useEffect, useMemo } from "react"; | |||||
| import React, { SyntheticEvent, useEffect, useMemo } from "react"; | |||||
| import RestartAlt from "@mui/icons-material/RestartAlt"; | import RestartAlt from "@mui/icons-material/RestartAlt"; | ||||
| import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
| import { Search, Clear, PersonAdd, PersonRemove } from "@mui/icons-material"; | import { Search, Clear, PersonAdd, PersonRemove } from "@mui/icons-material"; | ||||
| @@ -160,8 +160,8 @@ const StaffAllocation: React.FC<Props> = ({ | |||||
| }, [columnFilters]); | }, [columnFilters]); | ||||
| const [filters, setFilters] = React.useState(defaultFilterValues); | const [filters, setFilters] = React.useState(defaultFilterValues); | ||||
| const makeFilterSelect = React.useCallback( | const makeFilterSelect = React.useCallback( | ||||
| (filter: keyof StaffResult) => (event: SelectChangeEvent<string>) => { | |||||
| setFilters((f) => ({ ...f, [filter]: event.target.value })); | |||||
| (filter: keyof StaffResult) => (event: SyntheticEvent<Element, Event>, value: NonNullable<string>) => { | |||||
| setFilters((f) => ({ ...f, [filter]: value })); | |||||
| }, | }, | ||||
| [], | [], | ||||
| ); | ); | ||||
| @@ -239,20 +239,25 @@ const StaffAllocation: React.FC<Props> = ({ | |||||
| return ( | return ( | ||||
| <Grid key={`${filter.toString()}-${idx}`} item xs={3}> | <Grid key={`${filter.toString()}-${idx}`} item xs={3}> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <InputLabel size="small">{label}</InputLabel> | |||||
| <Select | |||||
| label={label} | |||||
| <Autocomplete | |||||
| disableClearable | |||||
| // disablePortal | |||||
| size="small" | size="small" | ||||
| noOptionsText={t(`No ${label}`)} | |||||
| value={filters[filter]} | value={filters[filter]} | ||||
| options={["All", ...(filterValues[filter] ?? [])]} | |||||
| getOptionLabel={(filterValue) => filterValue} | |||||
| isOptionEqualToValue={(option, value) => option === value} | |||||
| renderOption={(params, option) => { | |||||
| return ( | |||||
| <MenuItem {...params} key={option} value={option}> | |||||
| {option} | |||||
| </MenuItem> | |||||
| ); | |||||
| }} | |||||
| onChange={makeFilterSelect(filter)} | onChange={makeFilterSelect(filter)} | ||||
| > | |||||
| <MenuItem value={"All"}>{t("All")}</MenuItem> | |||||
| {filterValues[filter]?.map((option, index) => ( | |||||
| <MenuItem key={`${option}-${index}`} value={option}> | |||||
| {option} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| renderInput={(params) => <TextField {...params} variant="outlined" label={t(label)} />} | |||||
| /> | |||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| @@ -289,11 +294,11 @@ const StaffAllocation: React.FC<Props> = ({ | |||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| </Stack> | </Stack> | ||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| {/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| <Button variant="text" startIcon={<RestartAlt />} onClick={reset}> | <Button variant="text" startIcon={<RestartAlt />} onClick={reset}> | ||||
| {t("Reset")} | {t("Reset")} | ||||
| </Button> | </Button> | ||||
| </CardActions> | |||||
| </CardActions> */} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| {/* MUI X-Grid will throw an error if it is rendered without any dimensions; so not rendering when not active */} | {/* MUI X-Grid will throw an error if it is rendered without any dimensions; so not rendering when not active */} | ||||
| @@ -135,7 +135,7 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <Autocomplete | <Autocomplete | ||||
| disableClearable | disableClearable | ||||
| disablePortal | |||||
| // disablePortal | |||||
| noOptionsText={t("No Task List Source")} | noOptionsText={t("No Task List Source")} | ||||
| value={taskTemplates.find(taskTemplate => taskTemplate.id === selectedTaskTemplateId)} | value={taskTemplates.find(taskTemplate => taskTemplate.id === selectedTaskTemplateId)} | ||||
| options={[{id: "All", name: t("All tasks")}, ...taskTemplates.map(taskTemplate => ({id: taskTemplate.id, name: taskTemplate.name}))]} | options={[{id: "All", name: t("All tasks")}, ...taskTemplates.map(taskTemplate => ({id: taskTemplate.id, name: taskTemplate.name}))]} | ||||
| @@ -207,11 +207,11 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| allItemsLabel={t("Task Pool")} | allItemsLabel={t("Task Pool")} | ||||
| selectedItemsLabel={t("Project Task List")} | selectedItemsLabel={t("Project Task List")} | ||||
| /> | /> | ||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| {/* <CardActions sx={{ justifyContent: "flex-end" }}> | |||||
| <Button variant="text" startIcon={<RestartAlt />} onClick={onReset}> | <Button variant="text" startIcon={<RestartAlt />} onClick={onReset}> | ||||
| {t("Reset")} | {t("Reset")} | ||||
| </Button> | </Button> | ||||
| </CardActions> | |||||
| </CardActions> */} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| ); | ); | ||||
| @@ -20,7 +20,6 @@ type SearchParamNames = keyof SearchQuery; | |||||
| const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const { t } = useTranslation("projects"); | const { t } = useTranslation("projects"); | ||||
| console.log(projects) | |||||
| const [filteredProjects, setFilteredProjects] = useState(projects); | const [filteredProjects, setFilteredProjects] = useState(projects); | ||||
| @@ -62,7 +61,9 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | |||||
| const onProjectClick = useCallback( | const onProjectClick = useCallback( | ||||
| (project: ProjectResult) => { | (project: ProjectResult) => { | ||||
| router.push(`/projects/edit?id=${project.id}`); | |||||
| if (Boolean(project.mainProject)) { | |||||
| router.push(`/projects/edit/sub?id=${project.id}`); | |||||
| } else router.push(`/projects/edit?id=${project.id}`); | |||||
| }, | }, | ||||
| [router], | [router], | ||||
| ); | ); | ||||