@@ -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], | ||||
); | ); | ||||