|
|
@@ -24,8 +24,10 @@ import { |
|
|
|
TabsProps, |
|
|
|
Tab, |
|
|
|
Tabs, |
|
|
|
SelectChangeEvent, |
|
|
|
} from "@mui/material"; |
|
|
|
import differenceBy from "lodash/differenceBy"; |
|
|
|
import uniq from "lodash/uniq"; |
|
|
|
|
|
|
|
interface StaffResult { |
|
|
|
id: string; |
|
|
@@ -100,16 +102,17 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
const [selectedStaff, setSelectedStaff] = React.useState< |
|
|
|
typeof filteredStaff |
|
|
|
>(initiallySelectedStaff); |
|
|
|
const filters = React.useMemo<(keyof StaffResult)[]>( |
|
|
|
() => ["team", "grade"], |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
// Adding / Removing staff |
|
|
|
const addStaff = React.useCallback((staff: StaffResult) => { |
|
|
|
setSelectedStaff((staffs) => [...staffs, staff]); |
|
|
|
}, []); |
|
|
|
const removeStaff = React.useCallback((staff: StaffResult) => { |
|
|
|
setSelectedStaff((staffs) => staffs.filter((s) => s.id !== staff.id)); |
|
|
|
}, []); |
|
|
|
const clearStaff = React.useCallback(() => { |
|
|
|
setSelectedStaff([]); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const staffPoolColumns = React.useMemo<Column<StaffResult>[]>( |
|
|
|
() => [ |
|
|
@@ -145,6 +148,7 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
[removeStaff, t], |
|
|
|
); |
|
|
|
|
|
|
|
// Query related |
|
|
|
const [query, setQuery] = React.useState(""); |
|
|
|
const onQueryInputChange = React.useCallback< |
|
|
|
React.ChangeEventHandler<HTMLInputElement> |
|
|
@@ -154,18 +158,55 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
const clearQueryInput = React.useCallback(() => { |
|
|
|
setQuery(""); |
|
|
|
}, []); |
|
|
|
const columnFilters = React.useMemo<(keyof StaffResult)[]>( |
|
|
|
() => ["team", "grade"], |
|
|
|
[], |
|
|
|
); |
|
|
|
const filterValues = React.useMemo(() => { |
|
|
|
return columnFilters.reduce<{ [filter in keyof StaffResult]?: string[] }>( |
|
|
|
(acc, filter) => { |
|
|
|
return { |
|
|
|
...acc, |
|
|
|
[filter]: uniq(allStaff.map((staff) => staff[filter])), |
|
|
|
}; |
|
|
|
}, |
|
|
|
{}, |
|
|
|
); |
|
|
|
}, [columnFilters, allStaff]); |
|
|
|
const defaultFilterValues = React.useMemo(() => { |
|
|
|
return columnFilters.reduce<{ [filter in keyof StaffResult]?: string }>( |
|
|
|
(acc, filter) => { |
|
|
|
return { ...acc, [filter]: "All" }; |
|
|
|
}, |
|
|
|
{}, |
|
|
|
); |
|
|
|
}, [columnFilters]); |
|
|
|
const [filters, setFilters] = React.useState(defaultFilterValues); |
|
|
|
const makeFilterSelect = React.useCallback( |
|
|
|
(filter: keyof StaffResult) => (event: SelectChangeEvent<string>) => { |
|
|
|
setFilters((f) => ({ ...f, [filter]: event.target.value })); |
|
|
|
}, |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
setFilteredStaff( |
|
|
|
allStaff.filter( |
|
|
|
(staff) => |
|
|
|
staff.name.toLowerCase().includes(query) || |
|
|
|
staff.id.toLowerCase().includes(query) || |
|
|
|
staff.title.toLowerCase().includes(query), |
|
|
|
), |
|
|
|
allStaff.filter((staff) => { |
|
|
|
const q = query.toLowerCase(); |
|
|
|
return ( |
|
|
|
(staff.name.toLowerCase().includes(q) || |
|
|
|
staff.id.toLowerCase().includes(q) || |
|
|
|
staff.title.toLowerCase().includes(q)) && |
|
|
|
Object.entries(filters).every(([filterKey, filterValue]) => { |
|
|
|
const staffColumnValue = staff[filterKey as keyof StaffResult]; |
|
|
|
return staffColumnValue === filterValue || filterValue === "All"; |
|
|
|
}) |
|
|
|
); |
|
|
|
}), |
|
|
|
); |
|
|
|
}, [allStaff, query]); |
|
|
|
}, [allStaff, filters, query]); |
|
|
|
|
|
|
|
// Tab related |
|
|
|
const [tabIndex, setTabIndex] = React.useState(0); |
|
|
|
const handleTabChange = React.useCallback<NonNullable<TabsProps["onChange"]>>( |
|
|
|
(_e, newValue) => { |
|
|
@@ -174,6 +215,12 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
[], |
|
|
|
); |
|
|
|
|
|
|
|
const reset = React.useCallback(() => { |
|
|
|
clearQueryInput(); |
|
|
|
clearStaff(); |
|
|
|
setFilters(defaultFilterValues); |
|
|
|
}, [clearQueryInput, clearStaff, defaultFilterValues]); |
|
|
|
|
|
|
|
return ( |
|
|
|
<Card> |
|
|
|
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> |
|
|
@@ -201,7 +248,7 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Grid> |
|
|
|
{filters.map((filter, idx) => { |
|
|
|
{columnFilters.map((filter, idx) => { |
|
|
|
const label = staffPoolColumns.find( |
|
|
|
(c) => c.name === filter, |
|
|
|
)!.label; |
|
|
@@ -210,8 +257,18 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
<Grid key={`${filter.toString()}-${idx}`} item xs={3}> |
|
|
|
<FormControl fullWidth> |
|
|
|
<InputLabel size="small">{label}</InputLabel> |
|
|
|
<Select label={label} size="small"> |
|
|
|
<Select |
|
|
|
label={label} |
|
|
|
size="small" |
|
|
|
value={filters[filter]} |
|
|
|
onChange={makeFilterSelect(filter)} |
|
|
|
> |
|
|
|
<MenuItem value={"All"}>{t("All")}</MenuItem> |
|
|
|
{filterValues[filter]?.map((option, index) => ( |
|
|
|
<MenuItem key={`${option}-${index}`} value={option}> |
|
|
|
{option} |
|
|
|
</MenuItem> |
|
|
|
))} |
|
|
|
</Select> |
|
|
|
</FormControl> |
|
|
|
</Grid> |
|
|
@@ -220,7 +277,7 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
</Grid> |
|
|
|
<Tabs value={tabIndex} onChange={handleTabChange}> |
|
|
|
<Tab label={t("Staff Pool")} /> |
|
|
|
<Tab label={t("Allocated Staff")} /> |
|
|
|
<Tab label={`${t("Allocated Staff")} (${selectedStaff.length})`} /> |
|
|
|
</Tabs> |
|
|
|
<Box sx={{ marginInline: -3 }}> |
|
|
|
{tabIndex === 0 && ( |
|
|
@@ -240,7 +297,7 @@ const StaffAllocation: React.FC<Props> = ({ |
|
|
|
</Box> |
|
|
|
</Stack> |
|
|
|
<CardActions sx={{ justifyContent: "flex-end" }}> |
|
|
|
<Button variant="text" startIcon={<RestartAlt />}> |
|
|
|
<Button variant="text" startIcon={<RestartAlt />} onClick={reset}> |
|
|
|
{t("Reset")} |
|
|
|
</Button> |
|
|
|
</CardActions> |
|
|
|