Skip to content

Commit

Permalink
Merge pull request #217 from bettersg/feature/190-pickup-multiselect
Browse files Browse the repository at this point in the history
Feature/190 pickup multiselect
  • Loading branch information
neozhixuan authored Dec 21, 2023
2 parents f3e2660 + 6746655 commit 47069d5
Show file tree
Hide file tree
Showing 10 changed files with 360 additions and 225 deletions.
4 changes: 2 additions & 2 deletions client/components/map/FacilityCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const FacilityCard = ({
);
};

const AcceptedTab: React.FC<{ children: ReactNode }> = ({ children }) => {
export const AcceptedTab: React.FC<{ children: ReactNode }> = ({ children }) => {
return (
<Box
bg={"#CCECD5"}
Expand All @@ -183,7 +183,7 @@ const AcceptedTab: React.FC<{ children: ReactNode }> = ({ children }) => {
);
};

const UnacceptedTab: React.FC<{ children: ReactNode }> = ({ children }) => {
export const UnacceptedTab: React.FC<{ children: ReactNode }> = ({ children }) => {
return (
<Box bg={"#E0F0EF"} borderRadius={"42px"} minWidth={"fit-content"} padding={"5px 10px"}>
{children}
Expand Down
7 changes: 3 additions & 4 deletions client/components/pickup/ButtonRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ArrowBackIcon, ArrowLeftIcon } from "@chakra-ui/icons";
import { Button, Flex, Heading, Spacer, Box, ButtonGroup, Icon } from "@chakra-ui/react";
import { ArrowBackIcon } from "@chakra-ui/icons";
import { Button, Flex, Heading, Spacer, Box, ButtonGroup } from "@chakra-ui/react";
import { Dispatch, SetStateAction } from "react";
import { BiLeftArrow } from "react-icons/bi";
import { Pages } from "spa-pages/pageEnums";

type Props = {
Expand All @@ -10,7 +9,7 @@ type Props = {

const ButtonRow = ({ setPage }: Props) => {
return (
<Flex px={6}>
<Flex>
<Box>
<Heading size={"md"}>Your items:</Heading>
</Box>
Expand Down
89 changes: 72 additions & 17 deletions client/components/pickup/ItemsAndFilterRow.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,75 @@
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
import { Flex, theme, useDisclosure } from "@chakra-ui/react";
import { TEmptyItem, TItemSelection } from "app-context/SheetyContext/types";
import FilterButton from "./filterPopover";
import { SelectAndFilterBar, SelectedItemChips } from "spa-pages";
import { SelectAndFilterBar, multiselectOnChange, OptionType, checkboxChange } from "spa-pages";
import { ActionMeta, MultiValue } from "react-select";
import { OrgProps } from "spa-pages";

type Props = {
items: (TItemSelection | TEmptyItem)[];
setOrgs: Dispatch<SetStateAction<OrgProps[]>>;
sortPickups: (itemEntry: (TItemSelection | TEmptyItem)[]) => OrgProps[];
};

const ItemsAndFilterRow = ({ items }: Props) => {
const ItemsAndFilterRow = ({ items, setOrgs, sortPickups }: Props) => {
const colors = theme.colors;

// Multiselect Box
const selectOptions: OptionType[] = items.map((item, index) => ({
value: item.name,
label: item.name,
method: item.method,
idx: index,
}));

const [itemState, setItemState] = useState<(TItemSelection | TEmptyItem)[]>(items);
const [selectedOptions, setSelectedOptions] = useState<OptionType[]>([...selectOptions]);

const { isOpen: isFilterOpen, onOpen: onFilterOpen, onClose: onFilterClose } = useDisclosure();

const handleMultiselectOnChange = (
newValue: MultiValue<OptionType>,
actionMeta: ActionMeta<OptionType>,
) => {
const { updatedOptions, updatedItemState } = multiselectOnChange(
newValue,
actionMeta,
itemState,
selectedOptions,
);
setSelectedOptions(updatedOptions);
setItemState(updatedItemState);
setOrgs(sortPickups(updatedItemState));
};

// Handle changes in items selected in the Filter panel
const handleCheckboxChange = (e: ChangeEvent<HTMLInputElement>) => {
const { updatedItemState, updatedOptions } = checkboxChange(e, itemState, selectedOptions);
setSelectedOptions(updatedOptions);
setItemState(updatedItemState);
setOrgs(sortPickups(updatedItemState));
};

const selectAllItems = () => {
const selectOptions: OptionType[] = items.map((item, index) => ({
value: item.name,
label: item.name,
method: item.method,
idx: index,
}));
const itemState = items.map((item) => ({
name: item.name,
method: item.method,
}));

setItemState(itemState);
setSelectedOptions(selectOptions);
setOrgs(sortPickups(itemState));
};

return (
<Flex px={4}>
<Flex>
<Flex
justifyContent="space-between"
flexGrow={1}
Expand All @@ -22,23 +78,22 @@ const ItemsAndFilterRow = ({ items }: Props) => {
borderRadius="md"
>
<SelectAndFilterBar
selectedOptions={items.map((item, idx) => ({
label: item.name,
value: item.name,
method: item.method,
idx,
}))}
onMultiSelectChange={() => void 0}
selectOptions={items.map((item, idx) => ({
label: item.name,
value: item.name,
method: item.method,
idx,
}))}
selectedOptions={selectedOptions}
onMultiSelectChange={handleMultiselectOnChange}
selectOptions={selectOptions}
onFilterOpen={onFilterOpen}
enableBoxShadow={false}
/>
</Flex>
<FilterButton items={items} isOpen={isFilterOpen} onClose={onFilterClose} />
{/* Filter Panel */}
<FilterButton
isOpen={isFilterOpen}
onClose={onFilterClose}
handleCheckboxChange={handleCheckboxChange}
selectAllItems={selectAllItems}
itemState={itemState}
selectOptions={selectOptions}
/>
</Flex>
);
};
Expand Down
121 changes: 69 additions & 52 deletions client/components/pickup/OrgCard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { Text, Button, ButtonGroup, VStack, Heading, Flex, Accordion, AccordionItem, AccordionButton, Box, AccordionIcon, AccordionPanel, Spacer, theme } from "@chakra-ui/react";
import {
Text,
Button,
ButtonGroup,
VStack,
Heading,
Flex,
Box,
theme,
Divider,
} from "@chakra-ui/react";
import Link from "next/link";

import { Card, CardBody } from "@chakra-ui/card";
import { TSheetyPickupDetails } from "api/sheety/types";
import { MdOutlineScale } from "react-icons/md";
import { BiTimeFive } from "react-icons/bi";
import { BsCurrencyDollar } from "react-icons/bs";
import OrgLabel from "./OrgLabel";
import { TEmptyItem, TItemSelection } from "app-context/SheetyContext/types";
import { CheckIcon, SmallCloseIcon } from "@chakra-ui/icons";
import { AcceptedTab, UnacceptedTab } from "components/map";

type Props = {
orgDetails: TSheetyPickupDetails;
Expand All @@ -24,71 +36,76 @@ const OrgCard = (props: Props) => {
pricingTermsInSgd,
contactMethod,
contactDetail,
lastUpdated
lastUpdated,
} = props.orgDetails;
const { acceptedItems, notAcceptedItems } = props;
const numItems = acceptedItems.length + notAcceptedItems.length;
const colors = theme.colors;

return (
<Card mx={5} my={2} boxShadow="md" border="2px" borderColor={colors.gray[100]} >
<Card my={2} boxShadow="md" border="2px" borderColor={colors.gray[100]}>
<CardBody>
<VStack spacing={"3"} align="left">
<Heading size={"md"}>
{organisationName}
</Heading>
<Heading size={"md"}>{organisationName}</Heading>
<Flex wrap="wrap" columnGap={6} rowGap={1}>
<OrgLabel icon={MdOutlineScale} title="Min. Weight:" text={`${minimumWeightInKg} kg`} />
<OrgLabel
icon={MdOutlineScale}
title="Min. Weight:"
text={`${minimumWeightInKg} kg`}
/>
<OrgLabel icon={BiTimeFive} title="Pickup Hours:" text={time} />
<OrgLabel icon={BsCurrencyDollar} title="Service Cost:" text={`$${pricingTermsInSgd}`} />
<OrgLabel
icon={BsCurrencyDollar}
title="Service Cost:"
text={`$${pricingTermsInSgd}`}
/>
</Flex>
<Accordion allowToggle>
<AccordionItem border="none">
<h2>
<AccordionButton py={1} bgColor={colors.green[100]} border="1px" borderColor={colors.green[300]} borderRadius="md" _hover={{ bg: colors.green[100] }}>
<Box rowGap={2} as="b" flex="1" textAlign="left" fontSize="sm" textColor={colors.gray[700]}>
<CheckIcon boxSize="3" ml={1} mr={2} color={colors.green[400]} />
Accepted: {acceptedItems.length}/{numItems} items
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4} fontSize="sm">
<Box>
<Text>{acceptedItems.map((item) => item.name).join(", ")}</Text>
<Spacer mt={4} />
<Text as="b">They also accept these items:</Text>
<Text>{categoriesAccepted.slice(0, 1)}{categoriesAccepted.slice(1).toLowerCase().replaceAll("_", " ")}.</Text>
<Text>Please check their website for more info.</Text>
</Box>
</AccordionPanel>
</AccordionItem>
</Accordion>
{notAcceptedItems.length > 0 &&
<Accordion allowToggle>
<AccordionItem border="none">
<h2>
<AccordionButton py={1} bgColor={colors.red[100]} border="1px" borderColor={colors.red[300]} borderRadius="md" _hover={{ bg: colors.red[100] }}>
<Box rowGap={2} as="b" flex="1" textAlign="left" fontSize="sm" textColor={colors.gray[700]}>
<SmallCloseIcon boxSize="4" mr={2} color={colors.red[400]} />
Not Accepted: {notAcceptedItems.length}/{numItems} items
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4} fontSize="sm">
{notAcceptedItems.map((item) => item.name).join(", ")}
</AccordionPanel>
</AccordionItem>
</Accordion>
}
<ButtonGroup mt={4}>
<Divider />
<Box>
<Text color={"black"} fontWeight={500} mb={1}>
They accept {acceptedItems.length} of {numItems} items:
</Text>
<Flex gap={2} fontSize={"xs"} width={"100%"} wrap={"wrap"}>
{acceptedItems.map((item, idx) => (
<AcceptedTab key={idx}>{item.name}</AcceptedTab>
))}
{notAcceptedItems.map((item, idx) => (
<UnacceptedTab key={idx}>{item.name}</UnacceptedTab>
))}
</Flex>
</Box>
<Box>
<Text as="b">They also accept these items:</Text>
<Text>
{categoriesAccepted
.split(" ")
.map(
(category) =>
category.slice(0, 1) +
category.slice(1).toLowerCase().replaceAll("_", " "),
)
.join(" ")}
</Text>
</Box>
<ButtonGroup mt={2}>
<a href={website} target="_blank" rel="noreferrer">
<Button colorScheme={"teal"} variant={"outline"}>
<Button
colorScheme={"teal"}
variant={"outline"}
disabled={website ? false : true}
>
Website
</Button>
</a>
<a href={Number.isNaN(Number(contactDetail)) ? contactDetail : `tel:${contactDetail}`} target="_blank" rel="noreferrer">
<a
href={
Number.isNaN(Number(contactDetail))
? contactDetail
: `tel:${contactDetail}`
}
target="_blank"
rel="noreferrer"
>
<Button colorScheme={"teal"} variant={"solid"}>
Arrange Pickup!
</Button>
Expand Down
27 changes: 16 additions & 11 deletions client/components/pickup/OrgLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { Text, HStack, Icon } from "@chakra-ui/react";
import { IconType } from "react-icons";
import { COLORS } from "theme";

type Props = {
icon: IconType;
title: string;
text: string;
icon: IconType;
title: string;
text: string;
};

const OrgLabel = (props: Props) => {
return (
<HStack flexShrink={0}>
<Icon as={props.icon} boxSize={4} />
<Text as='b' fontSize="xs" >{props.title}</Text>
<Text fontSize="xs" >{props.text}</Text>
</HStack>
);
return (
<HStack flexShrink={0}>
<Icon as={props.icon} boxSize={4} />
<Text as="b" fontSize="sm" color={COLORS.teal} fontWeight={500}>
{props.title}
</Text>
<Text fontSize="sm" fontWeight={500}>
{props.text}
</Text>
</HStack>
);
};

export default OrgLabel;
export default OrgLabel;
30 changes: 17 additions & 13 deletions client/components/pickup/OrgList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Divider, Heading, Center, theme } from "@chakra-ui/react";
import { Box, Heading, Text } from "@chakra-ui/react";
import OrgCard from "./OrgCard";
import { OrgProps } from "spa-pages/components/PickupPage";

Expand All @@ -7,21 +7,25 @@ type Props = {
};

const OrgList = (props: Props) => {
const colors = theme.colors;

return (
<>
<Center>
<Divider borderColor={colors.gray[500]} w="85%" />
</Center>
<Heading size="lg" textAlign="center">
<Box>
<Heading size="md" textAlign="left" mb={4}>
Pick Up Services Near You
</Heading>
{props.sortedPossiblePickups.map((pickup) => (
<OrgCard key={pickup.organisation["s/n"]} orgDetails={pickup.organisation} acceptedItems={pickup.acceptedItems} notAcceptedItems={pickup.notAcceptedItems} />
))}
</>
{props.sortedPossiblePickups.length > 0 ? (
props.sortedPossiblePickups.map((pickup) => (
<OrgCard
key={pickup.organisation["s/n"]}
orgDetails={pickup.organisation}
acceptedItems={pickup.acceptedItems}
notAcceptedItems={pickup.notAcceptedItems}
/>
))
) : (
<Text textAlign={"center"}>No relevant pick up services were found. :(</Text>
)}
</Box>
);
};

export default OrgList;
export default OrgList;
2 changes: 1 addition & 1 deletion client/components/pickup/PickupCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const PickupCarousel = ({
};

return (
<Box className={styles.carouselbox} px={4} h={40}>
<Box className={styles.carouselbox} h={40}>
<Carousel
showThumbs={false}
showStatus={false}
Expand Down
Loading

0 comments on commit 47069d5

Please sign in to comment.