@@ -65,30 +65,3 @@ export const fetchProjectCategories = cache(async () => { | |||
}, | |||
); | |||
}); | |||
const mockProjects: ProjectResult[] = [ | |||
{ | |||
id: 1, | |||
code: "M1001", | |||
name: "Consultancy Project A", | |||
category: "Confirmed Project", | |||
team: "TW", | |||
client: "Client A", | |||
}, | |||
{ | |||
id: 2, | |||
code: "M1002", | |||
name: "Consultancy Project B", | |||
category: "Project to be bidded", | |||
team: "WY", | |||
client: "Client B", | |||
}, | |||
{ | |||
id: 3, | |||
code: "S1001", | |||
name: "Consultancy Project C", | |||
category: "Confirmed Project", | |||
team: "WY", | |||
client: "Client C", | |||
}, | |||
]; |
@@ -20,6 +20,9 @@ import { LabelGroup, LabelWithId, TransferListProps } from "./TransferList"; | |||
import { useTranslation } from "react-i18next"; | |||
import uniqBy from "lodash/uniqBy"; | |||
import groupBy from "lodash/groupBy"; | |||
import intersectionBy from "lodash/intersectionBy"; | |||
import union from "lodash/union"; | |||
import differenceBy from "lodash/differenceBy"; | |||
export const MultiSelectList: React.FC<TransferListProps> = ({ | |||
allItems, | |||
@@ -53,15 +56,28 @@ export const MultiSelectList: React.FC<TransferListProps> = ({ | |||
[allItems, onChange], | |||
); | |||
const handleToggleAll = useCallback( | |||
() => () => { | |||
if (selectedItems.length === allItems.length) { | |||
onChange([]); | |||
const handleToggleAll = useCallback(() => { | |||
if (selectedItems.length === allItems.length) { | |||
onChange([]); | |||
} else { | |||
onChange(allItems); | |||
} | |||
}, [allItems, onChange, selectedItems.length]); | |||
const handleToggleAllInGroup = useCallback( | |||
(groupItems: LabelWithId[]) => () => { | |||
const selectedGroupItems = intersectionBy( | |||
selectedItems, | |||
groupItems, | |||
"id", | |||
); | |||
if (selectedGroupItems.length !== groupItems.length) { | |||
onChange(union(selectedItems, groupItems)); | |||
} else { | |||
onChange(allItems); | |||
onChange(differenceBy(selectedItems, groupItems, "id")); | |||
} | |||
}, | |||
[allItems, onChange, selectedItems.length], | |||
[onChange, selectedItems], | |||
); | |||
const { t } = useTranslation(); | |||
@@ -117,7 +133,7 @@ export const MultiSelectList: React.FC<TransferListProps> = ({ | |||
direction="row" | |||
paddingY={1} | |||
paddingX={3} | |||
onClick={handleToggleAll()} | |||
onClick={handleToggleAll} | |||
> | |||
<ListItemIcon> | |||
<Checkbox | |||
@@ -141,11 +157,30 @@ export const MultiSelectList: React.FC<TransferListProps> = ({ | |||
</ListSubheader> | |||
{groups.flatMap((group) => { | |||
const groupItems = groupedItems[group.id]; | |||
const selectedGroupItems = intersectionBy( | |||
selectedItems, | |||
groupItems, | |||
"id", | |||
); | |||
if (!groupItems) return null; | |||
return [ | |||
<ListSubheader disableSticky key={`${group.id}-${group.name}`}> | |||
{group.name} | |||
<Stack | |||
onClick={handleToggleAllInGroup(groupItems)} | |||
direction="row" | |||
paddingX={1} | |||
> | |||
<Checkbox | |||
disableRipple | |||
checked={selectedGroupItems.length === groupItems.length} | |||
indeterminate={ | |||
selectedGroupItems.length !== groupItems.length && | |||
selectedGroupItems.length !== 0 | |||
} | |||
/> | |||
{group.name} | |||
</Stack> | |||
</ListSubheader>, | |||
...groupItems.map((item) => { | |||
return ( | |||
@@ -19,6 +19,8 @@ import ListSubheader from "@mui/material/ListSubheader"; | |||
import groupBy from "lodash/groupBy"; | |||
import uniqBy from "lodash/uniqBy"; | |||
import { useTranslation } from "react-i18next"; | |||
import union from "lodash/union"; | |||
import intersectionBy from "lodash/intersectionBy"; | |||
export interface LabelGroup { | |||
id: number; | |||
@@ -47,6 +49,10 @@ interface ItemListProps { | |||
items: LabelWithId[], | |||
checkedItems: LabelWithId[], | |||
) => React.MouseEventHandler; | |||
handleToggleAllInGroup: ( | |||
groupItems: LabelWithId[], | |||
checkedItems: LabelWithId[], | |||
) => React.MouseEventHandler; | |||
handleToggle: (item: LabelWithId) => React.MouseEventHandler; | |||
} | |||
@@ -56,6 +62,7 @@ const ItemList: React.FC<ItemListProps> = ({ | |||
label, | |||
handleToggle, | |||
handleToggleAll, | |||
handleToggleAllInGroup, | |||
}) => { | |||
const { t } = useTranslation(); | |||
const groups: LabelGroup[] = uniqBy( | |||
@@ -111,14 +118,34 @@ const ItemList: React.FC<ItemListProps> = ({ | |||
> | |||
{groups.map((group) => { | |||
const groupItems = groupedItems[group.id]; | |||
const selectedGroupItems = intersectionBy( | |||
checkedItems, | |||
groupItems, | |||
"id", | |||
); | |||
if (!groupItems) return null; | |||
return ( | |||
<React.Fragment key={group.id}> | |||
<ListSubheader | |||
disableSticky | |||
sx={{ paddingBlock: 2, lineHeight: 1.8 }} | |||
onClick={handleToggleAllInGroup(groupItems, checkedItems)} | |||
sx={{ | |||
paddingBlock: 2, | |||
lineHeight: 1.8, | |||
display: "flex", | |||
alignItems: "center", | |||
}} | |||
> | |||
<ListItemIcon> | |||
<Checkbox | |||
checked={selectedGroupItems.length === groupItems.length} | |||
indeterminate={ | |||
selectedGroupItems.length !== groupItems.length && | |||
selectedGroupItems.length !== 0 | |||
} | |||
/> | |||
</ListItemIcon> | |||
{group.name} | |||
</ListSubheader> | |||
{groupItems.map((item) => { | |||
@@ -210,6 +237,18 @@ const TransferList: React.FC<TransferListProps> = ({ | |||
setCheckedList(differenceBy(checkedList, rightListChecked, "id")); | |||
}; | |||
const handleToggleAllInGroup = React.useCallback( | |||
(groupItems: LabelWithId[], checkedItems: LabelWithId[]) => () => { | |||
const selectedGroupItems = intersectionBy(checkedItems, groupItems, "id"); | |||
if (selectedGroupItems.length !== groupItems.length) { | |||
setCheckedList(union(checkedList, groupItems)); | |||
} else { | |||
setCheckedList(differenceBy(checkedList, groupItems, "id")); | |||
} | |||
}, | |||
[checkedList], | |||
); | |||
return ( | |||
<Stack spacing={2} direction="row" alignItems="center" position="relative"> | |||
<ItemList | |||
@@ -218,6 +257,7 @@ const TransferList: React.FC<TransferListProps> = ({ | |||
label={allItemsLabel} | |||
handleToggleAll={handleToggleAll} | |||
handleToggle={handleToggle} | |||
handleToggleAllInGroup={handleToggleAllInGroup} | |||
/> | |||
<ItemList | |||
items={rightList} | |||
@@ -225,6 +265,7 @@ const TransferList: React.FC<TransferListProps> = ({ | |||
label={selectedItemsLabel} | |||
handleToggleAll={handleToggleAll} | |||
handleToggle={handleToggle} | |||
handleToggleAllInGroup={handleToggleAllInGroup} | |||
/> | |||
<Stack | |||
spacing={1} | |||