Procházet zdrojové kódy

Refactor resource and milestone and add styled data grif

tags/Baseline_30082024_FRONTEND_UAT
Wayne před 1 rokem
rodič
revize
b6dd135fb0
9 změnil soubory, kde provedl 266 přidání a 101 odebrání
  1. +2
    -0
      .gitignore
  2. +1
    -1
      src/app/api/projects/actions.ts
  3. +154
    -0
      src/components/CreateProject/MilestoneSection.tsx
  4. +7
    -100
      src/components/CreateProject/ResourceMilestone.tsx
  5. +74
    -0
      src/components/CreateProject/ResourceSection.tsx
  6. +22
    -0
      src/components/StyledDataGrid/StyledDataGrid.tsx
  7. +1
    -0
      src/components/StyledDataGrid/index.ts
  8. +4
    -0
      src/theme/devias-material-kit/components.ts
  9. +1
    -0
      src/theme/devias-material-kit/palette.ts

+ 2
- 0
.gitignore Zobrazit soubor

@@ -34,3 +34,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

.vscode

+ 1
- 1
src/app/api/projects/actions.ts Zobrazit soubor

@@ -23,7 +23,7 @@ export interface CreateProjectInputs {
tasks: {
[taskId: Task["id"]]: {
manhourAllocation: {
[grade: string]: number;
[gradeId: number]: number;
};
};
};


+ 154
- 0
src/components/CreateProject/MilestoneSection.tsx Zobrazit soubor

@@ -0,0 +1,154 @@
import { CreateProjectInputs } from "@/app/api/projects/actions";
import { TaskGroup } from "@/app/api/tasks";
import { Add, Delete } from "@mui/icons-material";
import {
Stack,
Typography,
Grid,
FormControl,
Box,
Button,
} from "@mui/material";
import {
GridColDef,
GridActionsCellItem,
GridToolbarContainer,
} from "@mui/x-data-grid";
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import "dayjs/locale/zh-hk";
import React, { useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";

interface Props {
taskGroupId: TaskGroup["id"];
}

interface FooterToolbarProps {
onAdd: () => void;
}

const MilestoneSection: React.FC<Props> = ({ taskGroupId }) => {
const { t } = useTranslation();
const {} = useFormContext<CreateProjectInputs>();
const columns = useMemo<GridColDef[]>(
() => [
{
type: "actions",
field: "actions",
headerName: t("Actions"),
getActions: () => [
<GridActionsCellItem
key="delete-action"
icon={<Delete />}
label={t("Remove")}
onClick={() => {}}
/>,
],
},
{
field: "description",
headerName: t("Payment Milestone Description"),
width: 300,
editable: true,
},
{
field: "date",
headerName: t("Payment Milestone Date"),
width: 200,
type: "date",
editable: true,
},
{
field: "amount",
headerName: t("Payment Milestone Amount"),
width: 300,
editable: true,
type: "number",
},
],
[t],
);

return (
<Stack gap={1}>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Milestone")}
</Typography>
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs>
<FormControl fullWidth>
<DatePicker
label={t("Stage Start Date")}
defaultValue={dayjs()}
/>
</FormControl>
</Grid>
<Grid item xs>
<FormControl fullWidth>
<DatePicker label={t("Stage End Date")} defaultValue={dayjs()} />
</FormControl>
</Grid>
</Grid>
</LocalizationProvider>
<Box
sx={(theme) => ({
marginBlockStart: 1,
marginInline: -3,
borderBottom: `1px solid ${theme.palette.divider}`,
})}
>
<StyledDataGrid
autoHeight
sx={{ "--DataGrid-overlayHeight": "100px" }}
disableColumnMenu
rows={[]}
columns={columns}
slots={{
footer: FooterToolbar,
noRowsOverlay: NoRowsOverlay,
}}
slotProps={{
footer: undefined,
}}
/>
</Box>
</Stack>
);
};

const NoRowsOverlay: React.FC = () => {
const { t } = useTranslation();
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
height="100%"
>
<Typography variant="caption">{t("Add some milestones!")}</Typography>
</Box>
);
};

const FooterToolbar: React.FC<FooterToolbarProps> = ({ onAdd }) => {
const { t } = useTranslation();
return (
<GridToolbarContainer sx={{ p: 2 }}>
<Button
variant="outlined"
startIcon={<Add />}
onClick={onAdd}
size="small"
>
{t("Add Milestone")}
</Button>
</GridToolbarContainer>
);
};

export default MilestoneSection;

+ 7
- 100
src/components/CreateProject/ResourceMilestone.tsx Zobrazit soubor

@@ -5,43 +5,29 @@ import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import Button from "@mui/material/Button";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useMemo, useState } from "react";
import CardActions from "@mui/material/CardActions";
import RestartAlt from "@mui/icons-material/RestartAlt";
import {
Alert,
Box,
FormControl,
Grid,
InputLabel,
List,
ListItemButton,
ListItemText,
MenuItem,
Paper,
Select,
SelectChangeEvent,
Stack,
TextField,
} from "@mui/material";
import { Task, TaskGroup } from "@/app/api/tasks";
import uniqBy from "lodash/uniqBy";
import { moneyFormatter } from "@/app/utils/formatUtil";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import { useFormContext } from "react-hook-form";
import { CreateProjectInputs } from "@/app/api/projects/actions";
import MilestoneSection from "./MilestoneSection";
import ResourceSection from "./ResourceSection";

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

interface ResourceSectionProps {
tasks: Task[];
defaultManhourBreakdownByGrade: { [grade: string]: number };
onSetManhours: (hours: number, taskId: Task["id"]) => void;
onAllocateManhours: () => void;
defaultManhourBreakdownByGrade?: { [gradeId: number]: number };
}

const ResourceMilestone: React.FC<Props> = ({ allTasks }) => {
@@ -97,7 +83,7 @@ const ResourceMilestone: React.FC<Props> = ({ allTasks }) => {
onSetManhours={() => {}}
onAllocateManhours={() => {}}
/>
<MilestoneSection />
<MilestoneSection taskGroupId={currentTaskGroupId} />
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button variant="text" startIcon={<RestartAlt />}>
{t("Reset")}
@@ -117,91 +103,12 @@ const ResourceMilestone: React.FC<Props> = ({ allTasks }) => {
);
};

const ResourceSection: React.FC<ResourceSectionProps> = ({
tasks,
onAllocateManhours,
onSetManhours,
defaultManhourBreakdownByGrade,
}) => {
const { t } = useTranslation();
const [selectedTaskId, setSelectedTaskId] = useState(tasks[0].id);
const makeOnTaskSelect = useCallback(
(taskId: Task["id"]): React.MouseEventHandler =>
() => {
return setSelectedTaskId(taskId);
},
[],
);

useEffect(() => {
setSelectedTaskId(tasks[0].id);
}, [tasks]);

return (
<Box marginBlock={4}>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Resource")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<Paper elevation={2}>
<List dense sx={{ maxHeight: 300, overflow: "auto" }}>
{tasks.map((task, index) => {
return (
<ListItemButton
selected={selectedTaskId === task.id}
key={`${task.id}-${index}`}
onClick={makeOnTaskSelect(task.id)}
>
<ListItemText primary={task.name} />
</ListItemButton>
);
})}
</List>
</Paper>
</Grid>
<Grid item xs={6}>
<TextField label={t("Mahours Allocated to Task")} fullWidth />
</Grid>
</Grid>
</Box>
);
};

const MilestoneSection: React.FC = () => {
const { t } = useTranslation();
return (
<Box>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Milestone")}
</Typography>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs>
<FormControl fullWidth>
<DatePicker
label={t("Stage Start Date")}
defaultValue={dayjs()}
/>
</FormControl>
</Grid>
<Grid item xs>
<FormControl fullWidth>
<DatePicker label={t("Stage End Date")} defaultValue={dayjs()} />
</FormControl>
</Grid>
</Grid>
</LocalizationProvider>
</Box>
);
};

const NoTaskState: React.FC = () => {
const { t } = useTranslation();
return (
<Card>
<CardContent>
<Alert severity="error">
<Alert severity="warning">
{t('Please add some tasks in "Project Task Setup" first!')}
</Alert>
</CardContent>


+ 74
- 0
src/components/CreateProject/ResourceSection.tsx Zobrazit soubor

@@ -0,0 +1,74 @@
import { Task } from "@/app/api/tasks";
import {
Box,
Typography,
Grid,
Paper,
List,
ListItemButton,
ListItemText,
TextField,
} from "@mui/material";
import { useState, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Props as ResourceMilestoneProps } from "./ResourceMilestone";

interface Props {
tasks: Task[];
defaultManhourBreakdownByGrade: ResourceMilestoneProps["defaultManhourBreakdownByGrade"];
onSetManhours: (hours: number, taskId: Task["id"]) => void;
onAllocateManhours: () => void;
}

const ResourceSection: React.FC<Props> = ({
tasks,
onAllocateManhours,
onSetManhours,
defaultManhourBreakdownByGrade,
}) => {
const { t } = useTranslation();
const [selectedTaskId, setSelectedTaskId] = useState(tasks[0].id);
const makeOnTaskSelect = useCallback(
(taskId: Task["id"]): React.MouseEventHandler =>
() => {
return setSelectedTaskId(taskId);
},
[],
);

useEffect(() => {
setSelectedTaskId(tasks[0].id);
}, [tasks]);

return (
<Box marginBlock={4}>
<Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Resource")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<Paper elevation={2}>
<List dense sx={{ maxHeight: 300, overflow: "auto" }}>
{tasks.map((task, index) => {
return (
<ListItemButton
selected={selectedTaskId === task.id}
key={`${task.id}-${index}`}
onClick={makeOnTaskSelect(task.id)}
>
<ListItemText primary={task.name} />
</ListItemButton>
);
})}
</List>
</Paper>
</Grid>
<Grid item xs={6}>
<TextField label={t("Mahours Allocated to Task")} fullWidth />
</Grid>
</Grid>
</Box>
);
};

export default ResourceSection;

+ 22
- 0
src/components/StyledDataGrid/StyledDataGrid.tsx Zobrazit soubor

@@ -0,0 +1,22 @@
import { styled } from "@mui/material";
import { DataGrid } from "@mui/x-data-grid";

const StyledDataGrid = styled(DataGrid)(({ theme }) => ({
"--unstable_DataGrid-radius": 0,
"& .MuiDataGrid-columnHeaders": {
backgroundColor: theme.palette.grey[50],
},
"& .MuiDataGrid-columnHeaderTitle": {
color: theme.palette.grey[700],
fontSize: 12,
fontWeight: 600,
lineHeight: 2,
letterSpacing: 0.5,
textTransform: "uppercase",
},
"& .MuiDataGrid-columnSeparator": {
color: theme.palette.primary.main,
},
}));

export default StyledDataGrid;

+ 1
- 0
src/components/StyledDataGrid/index.ts Zobrazit soubor

@@ -0,0 +1 @@
export { default } from "./StyledDataGrid";

+ 4
- 0
src/theme/devias-material-kit/components.ts Zobrazit soubor

@@ -340,6 +340,10 @@ const components: ThemeOptions["components"] = {
padding: {
paddingBlock: "1rem",
paddingInline: "1rem",
"&.MuiDataGrid-menuList": {
paddingBlock: "0.25rem",
paddingInline: "0.25rem",
},
},
},
},


+ 1
- 0
src/theme/devias-material-kit/palette.ts Zobrazit soubor

@@ -28,6 +28,7 @@ const palette = {
},
warning,
neutral,
grey: neutral,
};

export const paletteOptions: PaletteOptions = { ...palette, mode: "light" };


Načítá se…
Zrušit
Uložit