瀏覽代碼

Small fixes

tags/Baseline_30082024_FRONTEND_UAT
Wayne 1 年之前
父節點
當前提交
52ee059f1b
共有 9 個檔案被更改,包括 143 行新增82 行删除
  1. +49
    -10
      src/components/CreateProject/CreateProject.tsx
  2. +12
    -4
      src/components/CreateProject/ProjectClientDetails.tsx
  3. +40
    -50
      src/components/CreateProject/ResourceCapacity.tsx
  4. +7
    -7
      src/components/CreateProject/ResourceMilestone.tsx
  5. +11
    -3
      src/components/CreateProject/StaffAllocation.tsx
  6. +3
    -2
      src/components/CreateProject/TaskSetup.tsx
  7. +18
    -4
      src/components/ProjectSearch/ProjectSearch.tsx
  8. +2
    -2
      src/components/TaskTemplateSearch/TaskTemplateSearch.tsx
  9. +1
    -0
      src/theme/devias-material-kit/components.ts

+ 49
- 10
src/components/CreateProject/CreateProject.tsx 查看文件

@@ -14,13 +14,32 @@ import TaskSetup from "./TaskSetup";
import StaffAllocation from "./StaffAllocation"; import StaffAllocation from "./StaffAllocation";
import ResourceMilestone from "./ResourceMilestone"; import ResourceMilestone from "./ResourceMilestone";
import { Task } from "@/app/api/tasks"; import { Task } from "@/app/api/tasks";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import {
FieldErrors,
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from "react-hook-form";
import { CreateProjectInputs } from "@/app/api/projects/actions"; import { CreateProjectInputs } from "@/app/api/projects/actions";
import { Error } from "@mui/icons-material";


export interface Props { export interface Props {
allTasks: Task[]; allTasks: Task[];
} }


const hasErrorsInTab = (
tabIndex: number,
errors: FieldErrors<CreateProjectInputs>,
) => {
switch (tabIndex) {
case 0:
return errors.projectName;
default:
false;
}
};

const CreateProject: React.FC<Props> = ({ allTasks }) => { const CreateProject: React.FC<Props> = ({ allTasks }) => {
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -41,6 +60,16 @@ const CreateProject: React.FC<Props> = ({ allTasks }) => {
console.log(data); console.log(data);
}, []); }, []);


const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>(
(errors) => {
// Set the tab so that the focus will go there
if (errors.projectName) {
setTabIndex(0);
}
},
[],
);

const formProps = useForm<CreateProjectInputs>({ const formProps = useForm<CreateProjectInputs>({
defaultValues: { defaultValues: {
tasks: {}, tasks: {},
@@ -49,23 +78,33 @@ const CreateProject: React.FC<Props> = ({ allTasks }) => {
}, },
}); });


const errors = formProps.formState.errors;

return ( return (
<FormProvider {...formProps}> <FormProvider {...formProps}>
<Stack <Stack
spacing={2} spacing={2}
component="form" component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
> >
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable"> <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Project and Client Details")} />
<Tab label={t("Project Task Setup")} />
<Tab label={t("Staff Allocation")} />
<Tab label={t("Resource and Milestone")} />
<Tab
label={t("Project and Client Details")}
icon={
hasErrorsInTab(0, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
) : undefined
}
iconPosition="end"
/>
<Tab label={t("Project Task Setup")} iconPosition="end" />
<Tab label={t("Staff Allocation")} iconPosition="end" />
<Tab label={t("Resource and Milestone")} iconPosition="end" />
</Tabs> </Tabs>
{tabIndex === 0 && <ProjectClientDetails />}
{tabIndex === 1 && <TaskSetup allTasks={allTasks} />}
{tabIndex === 2 && <StaffAllocation />}
{tabIndex === 3 && <ResourceMilestone allTasks={allTasks} />}
{<ProjectClientDetails isActive={tabIndex === 0} />}
{<TaskSetup allTasks={allTasks} isActive={tabIndex === 1} />}
{<StaffAllocation isActive={tabIndex === 2} />}
{<ResourceMilestone allTasks={allTasks} isActive={tabIndex === 3} />}
<Stack direction="row" justifyContent="flex-end" gap={1}> <Stack direction="row" justifyContent="flex-end" gap={1}>
<Button <Button
variant="outlined" variant="outlined"


+ 12
- 4
src/components/CreateProject/ProjectClientDetails.tsx 查看文件

@@ -18,12 +18,17 @@ import Button from "@mui/material/Button";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import { CreateProjectInputs } from "@/app/api/projects/actions"; import { CreateProjectInputs } from "@/app/api/projects/actions";


const ProjectClientDetails: React.FC = () => {
const ProjectClientDetails: React.FC<{ isActive: boolean }> = ({
isActive,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { register } = useFormContext<CreateProjectInputs>();
const {
register,
formState: { errors },
} = useFormContext<CreateProjectInputs>();


return ( return (
<Card>
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent component={Stack} spacing={4}> <CardContent component={Stack} spacing={4}>
<Box> <Box>
<Typography variant="overline" display="block" marginBlockEnd={1}> <Typography variant="overline" display="block" marginBlockEnd={1}>
@@ -48,7 +53,10 @@ const ProjectClientDetails: React.FC = () => {
<TextField <TextField
label={t("Project Name")} label={t("Project Name")}
fullWidth fullWidth
{...register("projectName")}
{...register("projectName", {
required: "Project name required!",
})}
error={Boolean(errors.projectName)}
/> />
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>


+ 40
- 50
src/components/CreateProject/ResourceCapacity.tsx 查看文件

@@ -1,8 +1,6 @@
import { manhourFormatter } from "@/app/utils/formatUtil"; import { manhourFormatter } from "@/app/utils/formatUtil";
import { import {
Box, Box,
Card,
CardContent,
Stack, Stack,
Table, Table,
TableBody, TableBody,
@@ -94,55 +92,47 @@ const ResourceCapacity: React.FC<Props> = ({ items = mockItems }) => {
); );


return ( return (
<Card>
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Stack gap={2}>
<Typography variant="overline" display="block">
{t("Resource Capacity")}
</Typography>
<Box sx={{ marginInline: -3 }}>
<TableContainer>
<Table>
<TableHead>
<TableRow>
{columns.map((column, idx) => (
<TableCell key={`${column.name.toString()}${idx}`}>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => {
return (
<TableRow
hover
tabIndex={-1}
key={`${item.grade}-${index}`}
>
{columns.map((column, idx) => {
const columnName = column.name;
const cellData = item[columnName];
<Stack gap={2}>
<Typography variant="overline" display="block">
{t("Resource Capacity")}
</Typography>
<Box sx={{ marginInline: -3 }}>
<TableContainer>
<Table>
<TableHead>
<TableRow>
{columns.map((column, idx) => (
<TableCell key={`${column.name.toString()}${idx}`}>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => {
return (
<TableRow hover tabIndex={-1} key={`${item.grade}-${index}`}>
{columns.map((column, idx) => {
const columnName = column.name;
const cellData = item[columnName];


return (
<TableCell key={`${columnName.toString()}-${idx}`}>
{columnName !== "headcount" &&
typeof cellData === "number"
? manhourFormatter.format(cellData)
: cellData}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Box>
</Stack>
</CardContent>
</Card>
return (
<TableCell key={`${columnName.toString()}-${idx}`}>
{columnName !== "headcount" &&
typeof cellData === "number"
? manhourFormatter.format(cellData)
: cellData}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Box>
</Stack>
); );
}; };




+ 7
- 7
src/components/CreateProject/ResourceMilestone.tsx 查看文件

@@ -15,11 +15,9 @@ import {
MenuItem, MenuItem,
Select, Select,
SelectChangeEvent, SelectChangeEvent,
Stack,
} 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";
import { moneyFormatter } from "@/app/utils/formatUtil";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import { CreateProjectInputs } from "@/app/api/projects/actions"; import { CreateProjectInputs } from "@/app/api/projects/actions";
import MilestoneSection from "./MilestoneSection"; import MilestoneSection from "./MilestoneSection";
@@ -29,11 +27,13 @@ import ProjectTotalFee from "./ProjectTotalFee";
export interface Props { export interface Props {
allTasks: Task[]; allTasks: Task[];
defaultManhourBreakdownByGrade?: { [gradeId: number]: number }; defaultManhourBreakdownByGrade?: { [gradeId: number]: number };
isActive: boolean;
} }


const ResourceMilestone: React.FC<Props> = ({ const ResourceMilestone: React.FC<Props> = ({
allTasks, allTasks,
defaultManhourBreakdownByGrade, defaultManhourBreakdownByGrade,
isActive,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { getValues } = useFormContext<CreateProjectInputs>(); const { getValues } = useFormContext<CreateProjectInputs>();
@@ -65,7 +65,7 @@ const ResourceMilestone: React.FC<Props> = ({


return ( return (
<> <>
<Card>
<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> <InputLabel>{t("Task Stage")}</InputLabel>
@@ -93,7 +93,7 @@ const ResourceMilestone: React.FC<Props> = ({
</CardActions> </CardActions>
</CardContent> </CardContent>
</Card> </Card>
<Card>
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent> <CardContent>
<ProjectTotalFee taskGroups={taskGroups} /> <ProjectTotalFee taskGroups={taskGroups} />
</CardContent> </CardContent>
@@ -102,10 +102,10 @@ const ResourceMilestone: React.FC<Props> = ({
); );
}; };


const NoTaskState: React.FC = () => {
const NoTaskState: React.FC<Pick<Props, "isActive">> = ({ isActive }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Card>
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent> <CardContent>
<Alert severity="warning"> <Alert severity="warning">
{t('Please add some tasks in "Project Task Setup" first!')} {t('Please add some tasks in "Project Task Setup" first!')}
@@ -119,7 +119,7 @@ const ResourceMilestoneWrapper: React.FC<Props> = (props) => {
const { getValues } = useFormContext<CreateProjectInputs>(); const { getValues } = useFormContext<CreateProjectInputs>();


if (Object.keys(getValues("tasks")).length === 0) { if (Object.keys(getValues("tasks")).length === 0) {
return <NoTaskState />;
return <NoTaskState isActive={props.isActive} />;
} }


return <ResourceMilestone {...props} />; return <ResourceMilestone {...props} />;


+ 11
- 3
src/components/CreateProject/StaffAllocation.tsx 查看文件

@@ -93,9 +93,13 @@ const mockStaffs: StaffResult[] = [


interface Props { interface Props {
allStaff?: StaffResult[]; allStaff?: StaffResult[];
isActive: boolean;
} }


const StaffAllocation: React.FC<Props> = ({ allStaff = mockStaffs }) => {
const StaffAllocation: React.FC<Props> = ({
allStaff = mockStaffs,
isActive,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setValue, getValues } = useFormContext<CreateProjectInputs>(); const { setValue, getValues } = useFormContext<CreateProjectInputs>();


@@ -235,7 +239,7 @@ const StaffAllocation: React.FC<Props> = ({ allStaff = mockStaffs }) => {


return ( return (
<> <>
<Card>
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Stack gap={2}> <Stack gap={2}>
<Typography variant="overline" display="block"> <Typography variant="overline" display="block">
@@ -318,7 +322,11 @@ const StaffAllocation: React.FC<Props> = ({ allStaff = mockStaffs }) => {
</CardActions> </CardActions>
</CardContent> </CardContent>
</Card> </Card>
<ResourceCapacity />
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<ResourceCapacity />
</CardContent>
</Card>
</> </>
); );
}; };


+ 3
- 2
src/components/CreateProject/TaskSetup.tsx 查看文件

@@ -20,9 +20,10 @@ import { CreateProjectInputs } from "@/app/api/projects/actions";


interface Props { interface Props {
allTasks: Task[]; allTasks: Task[];
isActive: boolean;
} }


const TaskSetup: React.FC<Props> = ({ allTasks: tasks }) => {
const TaskSetup: React.FC<Props> = ({ allTasks: tasks, isActive }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { getValues, setValue } = useFormContext<CreateProjectInputs>(); const { getValues, setValue } = useFormContext<CreateProjectInputs>();
const currentTasks = getValues("tasks"); const currentTasks = getValues("tasks");
@@ -38,7 +39,7 @@ const TaskSetup: React.FC<Props> = ({ allTasks: tasks }) => {
}, [currentTasks, tasks]); }, [currentTasks, tasks]);


return ( return (
<Card>
<Card sx={{ display: isActive ? "block" : "none" }}>
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Typography variant="overline" display="block" marginBlockEnd={1}> <Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Task List Setup")} {t("Task List Setup")}


+ 18
- 4
src/components/ProjectSearch/ProjectSearch.tsx 查看文件

@@ -28,24 +28,28 @@ const ProjectSearch: React.FC<Props> = ({ projects }) => {
label: t("Client name"), label: t("Client name"),
paramName: "client", paramName: "client",
type: "select", type: "select",
options: ["A", "B"],
options: ["Client A", "Client B", "Client C"],
}, },
{ {
label: t("Project category"), label: t("Project category"),
paramName: "category", paramName: "category",
type: "select", type: "select",
options: ["A", "B"],
options: ["Confirmed Project", "Project to be bidded"],
}, },
{ {
label: t("Team"), label: t("Team"),
paramName: "team", paramName: "team",
type: "select", type: "select",
options: ["A", "B"],
options: ["TW", "WY"],
}, },
], ],
[t], [t],
); );


const onReset = useCallback(() => {
setFilteredProjects(projects);
}, [projects]);

const onProjectClick = useCallback((project: ProjectResult) => { const onProjectClick = useCallback((project: ProjectResult) => {
console.log(project); console.log(project);
}, []); }, []);
@@ -72,8 +76,18 @@ const ProjectSearch: React.FC<Props> = ({ projects }) => {
<SearchBox <SearchBox
criteria={searchCriteria} criteria={searchCriteria}
onSearch={(query) => { onSearch={(query) => {
console.log(query);
setFilteredProjects(
projects.filter(
(p) =>
p.code.toLowerCase().includes(query.code.toLowerCase()) &&
p.name.toLowerCase().includes(query.name.toLowerCase()) &&
(query.client === "All" || p.client === query.client) &&
(query.category === "All" || p.category === query.category) &&
(query.team === "All" || p.team === query.team),
),
);
}} }}
onReset={onReset}
/> />
<SearchResults<ProjectResult> <SearchResults<ProjectResult>
items={filteredProjects} items={filteredProjects}


+ 2
- 2
src/components/TaskTemplateSearch/TaskTemplateSearch.tsx 查看文件

@@ -55,8 +55,8 @@ const TaskTemplateSearch: React.FC<Props> = ({ taskTemplates }) => {
setFilteredTemplates( setFilteredTemplates(
taskTemplates.filter( taskTemplates.filter(
(task) => (task) =>
task.code.toLowerCase().includes(query.code) &&
task.name.toLowerCase().includes(query.name),
task.code.toLowerCase().includes(query.code.toLowerCase()) &&
task.name.toLowerCase().includes(query.name.toLowerCase()),
), ),
); );
}} }}


+ 1
- 0
src/theme/devias-material-kit/components.ts 查看文件

@@ -277,6 +277,7 @@ const components: ThemeOptions["components"] = {
fontWeight: 500, fontWeight: 500,
lineHeight: 1.71, lineHeight: 1.71,
minWidth: "auto", minWidth: "auto",
minHeight: 48,
paddingLeft: 0, paddingLeft: 0,
paddingRight: 0, paddingRight: 0,
textTransform: "none", textTransform: "none",


Loading…
取消
儲存