Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/assign unassign tags #1988

Merged
merged 21 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions front/src/utils/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@ import { FragmentOf, ResultOf } from "gql.tada";

import { IDropdownOption } from "components/Form/SelectField";
import { ACTION_OPTIONS_FOR_BOXESVIEW_QUERY } from "views/Boxes/BoxesView";
import { LOCATION_BASIC_FIELDS_FRAGMENT } from "queries/fragments";
import { LOCATION_BASIC_FIELDS_FRAGMENT, TAG_BASIC_FIELDS_FRAGMENT } from "queries/fragments";

export function locationToDropdownOptionTransformer(
locations: FragmentOf<typeof LOCATION_BASIC_FIELDS_FRAGMENT>[],
): IDropdownOption[] {
return (
locations
?.filter(
(location) =>
location?.defaultBoxState !== "Lost" &&
location?.defaultBoxState !== "Scrap",
(location) => location?.defaultBoxState !== "Lost" && location?.defaultBoxState !== "Scrap",
)
?.sort((a, b) => Number(a?.seq) - Number(b?.seq))
?.map((location) => ({
label: `${location.name}${location.defaultBoxState !== "InStock"
? ` - Boxes are ${location.defaultBoxState}`
: ""
}`,
label: `${location.name}${
location.defaultBoxState !== "InStock" ? ` - Boxes are ${location.defaultBoxState}` : ""
}`,
value: location.id,
})) ?? []
);
Expand All @@ -31,10 +28,7 @@ export function shipmentToDropdownOptionTransformer(
): IDropdownOption[] {
return (
shipments
?.filter(
(shipment) =>
shipment.state === "Preparing" && shipment.sourceBase.id === baseId,
)
?.filter((shipment) => shipment.state === "Preparing" && shipment.sourceBase.id === baseId)
?.map((shipment) => ({
__typename: "Shipment", // Add this line to ensure __typename is set to "Shipment"
label: `${shipment.targetBase.name} - ${shipment.targetBase.organisation.name}`,
Expand All @@ -43,3 +37,16 @@ export function shipmentToDropdownOptionTransformer(
})) || []
);
}

export function tagToDropdownOptionsTransformer(
tags: FragmentOf<typeof TAG_BASIC_FIELDS_FRAGMENT>[],
): IDropdownOption[] {
return (
tags?.map((tag) => ({
__typename: "Tag",
label: tag.name,
value: tag.id,
color: tag.color,
})) || []
);
}
4 changes: 2 additions & 2 deletions front/src/views/Boxes/BoxesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { graphql } from "../../../../graphql/graphql";
import {
locationToDropdownOptionTransformer,
shipmentToDropdownOptionTransformer,
tagToDropdownOptionsTransformer,
} from "utils/transformers";
import { Column } from "react-table";
import { useTableConfig } from "hooks/hooks";
Expand Down Expand Up @@ -130,7 +131,6 @@ function Boxes() {
hiddenColumns: [
"gender",
"size",
"tags",
"shipment",
"comment",
"age",
Expand Down Expand Up @@ -300,7 +300,6 @@ function Boxes() {
[isPopoverOpen, setIsPopoverOpen.off, setIsPopoverOpen.on],
);

// TODO: pass tag options to BoxesActionsAndTable
return (
<>
<BreadcrumbNavigation items={[{ label: "Aid Inventory" }, { label: "Manage Boxes" }]} />
Expand All @@ -316,6 +315,7 @@ function Boxes() {
locationOptions={locationToDropdownOptionTransformer(
actionOptionsData.base?.locations ?? [],
)}
tagOptions={tagToDropdownOptionsTransformer(actionOptionsData?.base?.tags ?? [])}
/>
</>
);
Expand Down
105 changes: 105 additions & 0 deletions front/src/views/Boxes/components/AssignTagsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState } from "react";
import { Row } from "react-table";

import { BoxRow } from "./types";
import { useNotification } from "hooks/useNotification";
import { BiTag } from "react-icons/bi";
import { Box, Button, VStack } from "@chakra-ui/react";

import { Select } from "chakra-react-select";
import { IDropdownOption } from "components/Form/SelectField";

interface AssignTagsButtonProps {
onAssignTags: (tagIds: string[]) => void;
selectedBoxes: Row<BoxRow>[];
allTagOptions: IDropdownOption[];
}

const AssignTagsButton: React.FC<AssignTagsButtonProps> = ({
onAssignTags,
selectedBoxes,
allTagOptions,
}) => {
const { createToast } = useNotification();

const [isInputOpen, setIsInputOpen] = useState(false);
const [selectedTagOptions, setSelectedTagOptions] = useState<IDropdownOption[]>([]);

const handleOpenInput = () => {
if (selectedBoxes.length === 0) {
createToast({
type: "warning",
message: `Please select a box to assign tags`,
});
}
if (selectedBoxes.length !== 0) {
setIsInputOpen(true);
}
};

const handleConfirmAssignTags = () => {
onAssignTags(selectedTagOptions.map((tag) => tag.value));
setIsInputOpen(false);
setSelectedTagOptions([]);
};

return (
<VStack spacing={2}>
<Box w="full" alignSelf="start">
<Button
w="full"
justifyContent="flex-start"
padding={4}
iconSpacing={2}
onClick={(e) => {
e.stopPropagation();
handleOpenInput();
}}
leftIcon={<BiTag />}
variant="ghost"
data-testid="assign-tags-button"
>
Add Tags
</Button>
</Box>
{isInputOpen && (
<>
<Box maxWidth={230}>
<Select
placeholder="Type to find tags"
isSearchable
tagVariant="outline"
colorScheme="black"
useBasicStyles
focusBorderColor="blue.500"
chakraStyles={{
control: (provided) => ({
...provided,
border: "2px",
borderRadius: "0",
}),
multiValue: (provided, state) => ({
...provided,
background: state.data?.color,
}),
}}
isMulti
options={allTagOptions}
value={selectedTagOptions}
onChange={(s: any) => {
setSelectedTagOptions(s);
}}
/>
</Box>
<Box marginRight="10px" alignSelf="end" marginBottom="20px">
<Button borderRadius={4} colorScheme="blue" onClick={handleConfirmAssignTags}>
Apply
</Button>
</Box>
</>
)}
</VStack>
);
};

export default AssignTagsButton;
32 changes: 30 additions & 2 deletions front/src/views/Boxes/components/BoxesActionsAndTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ import ExportToCsvButton from "./ExportToCsvButton";
import { FaTruckArrowRight } from "react-icons/fa6";
import { BsBox2HeartFill } from "react-icons/bs";
import MakeLabelsButton from "./MakeLabelsButton";
import AssignTagsButton from "./AssignTagsButton";
import { IDropdownOption } from "components/Form/SelectField";
import { useAssignTags } from "hooks/useAssignTags";

export interface IBoxesActionsAndTableProps {
tableConfig: IUseTableConfigReturnType;
onRefetch: (variables?: BoxesForBoxesViewVariables) => void;
boxesQueryRef: QueryRef<BoxesForBoxesViewQuery>;
locationOptions: { label: string; value: string }[];
shipmentOptions: { label: string; value: string }[];
tagOptions: IDropdownOption[];
availableColumns: Column<BoxRow>[];
}

Expand All @@ -38,6 +42,7 @@ function BoxesActionsAndTable({
boxesQueryRef,
locationOptions,
shipmentOptions,
tagOptions,
availableColumns,
}: IBoxesActionsAndTableProps) {
const navigate = useNavigate();
Expand Down Expand Up @@ -222,11 +227,24 @@ function BoxesActionsAndTable({
deleteBoxes(selectedBoxes.map((box) => box.values as IBoxBasicFields));
}, [deleteBoxes, selectedBoxes]);

// Assign Tags to Boxes
const { assignTags, isLoading: isAssignTagsLoading } = useAssignTags();
const onAssignTags = useCallback(
async (tagIds: string[]) => {
await assignTags(
selectedBoxes.map((box) => box.values.labelIdentifier),
tagIds.map((id) => parseInt(id, 10)),
);
},
[assignTags, selectedBoxes],
);

const actionsAreLoading =
moveBoxesAction.isLoading ||
isAssignBoxesToShipmentLoading ||
isUnassignBoxesFromShipmentsLoading ||
isDeleteBoxesLoading;
isDeleteBoxesLoading ||
isAssignTagsLoading;

const actionButtons = useMemo(
() => [
Expand All @@ -246,7 +264,7 @@ function BoxesActionsAndTable({
isDisabled={actionsAreLoading || shipmentOptions.length === 0}
key="assign-to-shipment"
/>,
<Menu key="box-actions">
<Menu key="box-actions" closeOnSelect={false}>
<MenuButton as={Button}>
<BsBox2HeartFill />
</MenuButton>
Expand All @@ -263,6 +281,14 @@ function BoxesActionsAndTable({
<MenuItem>
<ExportToCsvButton selectedBoxes={selectedBoxes} key="export-csv" />
</MenuItem>
<Menu>
<AssignTagsButton
selectedBoxes={selectedBoxes}
key="assign-tags"
onAssignTags={onAssignTags}
allTagOptions={tagOptions}
/>
</Menu>
<MenuItem>
<MakeLabelsButton selectedBoxes={selectedBoxes} key="make-labels" />
</MenuItem>
Expand All @@ -286,7 +312,9 @@ function BoxesActionsAndTable({
onDeleteBoxes,
selectedBoxes,
thereIsABoxMarkedForShipmentSelected,
tagOptions,
onUnassignBoxesToShipment,
onAssignTags,
],
);

Expand Down