diff --git a/src/components/matches/MatchDetail.tsx b/src/components/matches/MatchDetail.tsx index 2f49255..878b980 100644 --- a/src/components/matches/MatchDetail.tsx +++ b/src/components/matches/MatchDetail.tsx @@ -1,13 +1,6 @@ import { MatchVariant, MatchWithDetails } from "@boklisten/bl-model"; import { ArrowBack } from "@mui/icons-material"; -import { - Alert, - Button, - Card, - Container, - Skeleton, - Typography, -} from "@mui/material"; +import { Alert, Button, Card, Container, Skeleton } from "@mui/material"; import React from "react"; import useSWR from "swr"; @@ -66,7 +59,6 @@ const MatchDetail = ({ matchId }: { matchId: string }) => { > - Overlevering av bøker {match._variant === MatchVariant.StandMatch && ( diff --git a/src/components/matches/MeetingInfo.tsx b/src/components/matches/MeetingInfo.tsx index 7736b3a..13aedca 100644 --- a/src/components/matches/MeetingInfo.tsx +++ b/src/components/matches/MeetingInfo.tsx @@ -5,39 +5,39 @@ import { Box, Typography } from "@mui/material"; import React from "react"; import DynamicLink from "@/components/DynamicLink"; -import { formatDatetime } from "@/components/matches/matchesList/helper"; +import { FormattedDatetime } from "@/components/matches/matchesList/helper"; const MeetingInfo = ({ match }: { match: MatchWithDetails }) => { const meetingTime = match.meetingInfo.date; const meetingLocation = match.meetingInfo.location; return ( - - - - - {meetingLocation} - - + {(meetingTime && ( - <> - - {formatDatetime(new Date(meetingTime))} - - + )) || ( - <> - - Du kan møte opp når som helst i{" "} - - skolens åpningstider - - - + + Du kan møte opp når som helst i{" "} + + skolens åpningstider + + )} + + + {meetingLocation} + ); }; diff --git a/src/components/matches/OtherPersonContact.tsx b/src/components/matches/OtherPersonContact.tsx index 635b0d7..207a0a8 100644 --- a/src/components/matches/OtherPersonContact.tsx +++ b/src/components/matches/OtherPersonContact.tsx @@ -27,15 +27,37 @@ const OtherPersonContact = ({ sx={{ display: "flex", alignItems: "center", justifyContent: "left" }} > - - {otherPerson.name} - - - {otherPerson.phone} + + {otherPerson.name},{" "} + + {formatPhoneNumber(otherPerson.phone)} - + ); }; +function formatPhoneNumber(number: string): string { + if (/\d{8}/.exec(number) !== null) { + return ( + number.slice(0, 3) + " " + number.slice(3, 5) + " " + number.slice(5, 8) + ); + } + if (/\d{10}/.exec(number) !== null) { + return ( + number.slice(2) + + " " + + number.slice(2, 5) + + " " + + number.slice(5, 7) + + " " + + number.slice(7, 10) + ); + } + return number; +} + export default OtherPersonContact; diff --git a/src/components/matches/StandMatchDetail.tsx b/src/components/matches/StandMatchDetail.tsx index c5929bc..bc12c9f 100644 --- a/src/components/matches/StandMatchDetail.tsx +++ b/src/components/matches/StandMatchDetail.tsx @@ -1,4 +1,4 @@ -import { Alert } from "@mui/material"; +import { Alert, Typography } from "@mui/material"; import React from "react"; import { @@ -7,6 +7,7 @@ import { ItemStatus, MatchHeader, } from "@/components/matches/matches-helper"; +import { StandMatchTitle } from "@/components/matches/matchesList/helper"; import ProgressBar from "@/components/matches/matchesList/ProgressBar"; import MatchItemTable from "@/components/matches/MatchItemTable"; import MeetingInfo from "@/components/matches/MeetingInfo"; @@ -49,6 +50,10 @@ const StandMatchDetail = ({ return ( <> + + + + {isFulfilled && ( Du har mottatt og levert alle bøkene for denne overleveringen. @@ -99,7 +104,7 @@ const StandMatchDetail = ({ )} - Du skal på stand ved + Du skal på stand: Kontaktinformasjon diff --git a/src/components/matches/UserMatchDetail.tsx b/src/components/matches/UserMatchDetail.tsx index 3f7834e..c6a4e0b 100644 --- a/src/components/matches/UserMatchDetail.tsx +++ b/src/components/matches/UserMatchDetail.tsx @@ -7,6 +7,7 @@ import { ItemStatus, MatchHeader, } from "@/components/matches/matches-helper"; +import { UserMatchTitle } from "@/components/matches/matchesList/helper"; import ProgressBar from "@/components/matches/matchesList/ProgressBar"; import MatchItemTable from "@/components/matches/MatchItemTable"; import MeetingInfo from "@/components/matches/MeetingInfo"; @@ -48,6 +49,10 @@ const UserMatchDetail = ({ return ( <> + + + + {isFulfilled && ( Du har {isSender ? "levert" : "mottatt"} alle bøkene for denne @@ -83,8 +88,8 @@ const UserMatchDetail = ({ Du skal møte - + {!isSender && !isFulfilled && ( <> diff --git a/src/components/matches/matchesList/MatchListItemBox.tsx b/src/components/matches/matchesList/MatchListItemBox.tsx index 6ffdd18..487c3f3 100644 --- a/src/components/matches/matchesList/MatchListItemBox.tsx +++ b/src/components/matches/matchesList/MatchListItemBox.tsx @@ -1,8 +1,14 @@ -import { Button, Card, CardActions, CardContent } from "@mui/material"; +import { + Button, + Card, + CardActionArea, + CardActions, + CardContent, +} from "@mui/material"; +import { green, grey } from "@mui/material/colors"; import React, { PropsWithChildren } from "react"; import DynamicLink from "@/components/DynamicLink"; - const MatchListItemBox: React.FC< PropsWithChildren<{ finished: boolean; matchId: string }> > = ({ finished, matchId, children }) => { @@ -10,17 +16,23 @@ const MatchListItemBox: React.FC< - {children} - - - + + {children} + + + + ); }; diff --git a/src/components/matches/matchesList/MatchListItemGroups.tsx b/src/components/matches/matchesList/MatchListItemGroups.tsx index 6ec4602..65c662e 100644 --- a/src/components/matches/matchesList/MatchListItemGroups.tsx +++ b/src/components/matches/matchesList/MatchListItemGroups.tsx @@ -2,50 +2,33 @@ import { MatchVariant, MatchWithDetails } from "@boklisten/bl-model"; import { Typography } from "@mui/material"; import React from "react"; -import { - formatDatetime, - sectionStyle, - getSortedMatchGroups, -} from "@/components/matches/matchesList/helper"; +import { sectionStyle } from "@/components/matches/matchesList/helper"; import StandMatchListItem from "@/components/matches/matchesList/StandMatchListItem"; import UserMatchListItem from "@/components/matches/matchesList/UserMatchListItem"; -import { GroupedMatches } from "@/utils/types"; export const MatchListItemGroups: React.FC<{ - groups: GroupedMatches; + matches: MatchWithDetails[]; userId: string; - heading: string; -}> = ({ groups, userId, heading }) => { + heading?: string; +}> = ({ matches, userId, heading }) => { return (
- {heading} - {getSortedMatchGroups(groups).map((key) => { - const { time, location } = groups.keyToData.get(key)!; - return ( -
- - {time ? formatDatetime(new Date(time)) : ""} {location} - - {groups.matchesByKey - .get(key)! - .map((match) => - match._variant === MatchVariant.StandMatch ? ( - - ) : ( - - ), - )} -
- ); - })} + {heading && {heading}} + {matches.map((match) => + match._variant === MatchVariant.StandMatch ? ( + + ) : ( + + ), + )}
); }; diff --git a/src/components/matches/matchesList/MatchesList.tsx b/src/components/matches/matchesList/MatchesList.tsx index 1cafa29..7563add 100644 --- a/src/components/matches/matchesList/MatchesList.tsx +++ b/src/components/matches/matchesList/MatchesList.tsx @@ -1,4 +1,4 @@ -import { MatchVariant, MatchWithDetails } from "@boklisten/bl-model"; +import { MatchWithDetails } from "@boklisten/bl-model"; import { Alert, Skeleton } from "@mui/material"; import React from "react"; import useSWR from "swr"; @@ -9,11 +9,9 @@ import { isMatchFulfilled, isUserSenderInMatch, } from "@/components/matches/matches-helper"; -import { groupMatchesByTimeAndLocation } from "@/components/matches/matchesList/helper"; import { MatchListItemGroups } from "@/components/matches/matchesList/MatchListItemGroups"; import ProgressBar from "@/components/matches/matchesList/ProgressBar"; import BL_CONFIG from "@/utils/bl-config"; -import { StandMatchWithDetails, UserMatchWithDetails } from "@/utils/types"; export const MatchesList: React.FC = () => { const { data: accessToken, error: tokenError } = useSWR("userId", () => @@ -22,9 +20,6 @@ export const MatchesList: React.FC = () => { const userId = accessToken?.details; const { data: matches, error: matchesError } = useSWR( `${BL_CONFIG.collection.match}/me`, - // The following line errors in WebStorm for some reason, but it's allowed. - // WebStorm accepts it wrapped in parentheses, but then prettier doesn't, so - // just ignore it. apiFetcher, { refreshInterval: 5000 }, ); @@ -36,25 +31,25 @@ export const MatchesList: React.FC = () => { if (matches === undefined) { return ; } + const sortedMatches = matches.sort((a, b) => { + if (!a.meetingInfo.date) { + return b.meetingInfo.date ? 1 : 0; + } else if (!b.meetingInfo.date) { + return -1; + } - const standMatches = matches - .filter((match) => match._variant === MatchVariant.StandMatch) - .map((standMatch) => standMatch as StandMatchWithDetails) - .sort((a, b) => - isMatchFulfilled(a, false) ? 1 : isMatchFulfilled(b, false) ? -1 : 0, - ); - const userMatches = matches - .filter((match) => match._variant === MatchVariant.UserMatch) - .map((userMatch) => userMatch as UserMatchWithDetails) - .sort((a, b) => - isMatchFulfilled(a, isUserSenderInMatch(a, userId)) - ? 1 - : isMatchFulfilled(b, isUserSenderInMatch(b, userId)) - ? -1 - : 0, - ); - const standMatchesByTime = groupMatchesByTimeAndLocation(standMatches); - const userMatchesByTime = groupMatchesByTimeAndLocation(userMatches); + if (a.meetingInfo.date > b.meetingInfo.date) return 1; + if (a.meetingInfo.date < b.meetingInfo.date) return -1; + + return 0; + }); + + const unfulfilledMatches = sortedMatches.filter( + (match) => !isMatchFulfilled(match, isUserSenderInMatch(match, userId)), + ); + const fulfilledMatches = sortedMatches.filter((match) => + isMatchFulfilled(match, isUserSenderInMatch(match, userId)), + ); if (matches.length === 0) { return Du har ingen overleveringer :); @@ -75,19 +70,15 @@ export const MatchesList: React.FC = () => { } /> - {userMatches.length > 0 && ( - + {unfulfilledMatches.length > 0 && ( + )} - {standMatches.length > 0 && ( + {fulfilledMatches.length > 0 && ( )} diff --git a/src/components/matches/matchesList/StandMatchListItem.tsx b/src/components/matches/matchesList/StandMatchListItem.tsx index dbd0ad4..d2e0007 100644 --- a/src/components/matches/matchesList/StandMatchListItem.tsx +++ b/src/components/matches/matchesList/StandMatchListItem.tsx @@ -1,4 +1,4 @@ -import { Box } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import React from "react"; import { @@ -6,9 +6,13 @@ import { isMatchBegun, isMatchFulfilled, } from "@/components/matches/matches-helper"; -import { formatActionsString } from "@/components/matches/matchesList/helper"; +import { + formatActionsString, + StandMatchTitle, +} from "@/components/matches/matchesList/helper"; import MatchListItemBox from "@/components/matches/matchesList/MatchListItemBox"; import ProgressBar from "@/components/matches/matchesList/ProgressBar"; +import MeetingInfo from "@/components/matches/MeetingInfo"; import { StandMatchWithDetails } from "@/utils/types"; const StandMatchListItem: React.FC<{ @@ -25,6 +29,9 @@ const StandMatchListItem: React.FC<{ const isFulfilled = isMatchFulfilled(match, false); return ( + + + {isBegun && ( <> {hasHandoffItems && ( @@ -66,6 +73,7 @@ const StandMatchListItem: React.FC<{ )} + {!isFulfilled && } ); }; diff --git a/src/components/matches/matchesList/UserMatchListItem.tsx b/src/components/matches/matchesList/UserMatchListItem.tsx index 7fcc713..3d18374 100644 --- a/src/components/matches/matchesList/UserMatchListItem.tsx +++ b/src/components/matches/matchesList/UserMatchListItem.tsx @@ -1,4 +1,3 @@ -import { KeyboardDoubleArrowRight } from "@mui/icons-material"; import { Box, Typography } from "@mui/material"; import React from "react"; @@ -8,13 +7,15 @@ import { isMatchFulfilled, isUserSenderInMatch, } from "@/components/matches/matches-helper"; -import { formatActionsString } from "@/components/matches/matchesList/helper"; +import { + formatActionsString, + UserMatchTitle, +} from "@/components/matches/matchesList/helper"; import MatchListItemBox from "@/components/matches/matchesList/MatchListItemBox"; import ProgressBar from "@/components/matches/matchesList/ProgressBar"; +import MeetingInfo from "@/components/matches/MeetingInfo"; import { UserMatchWithDetails } from "@/utils/types"; -const me = Meg; - const UserMatchListItem: React.FC<{ match: UserMatchWithDetails; currentUserId: string; @@ -27,26 +28,11 @@ const UserMatchListItem: React.FC<{ match, isSender, ); - const HeaderLevel = "h4"; return ( - {isSender ? ( - - {me}{" "} - {" "} - {match.receiverDetails.name} - - ) : ( - - {match.senderDetails.name}{" "} - {" "} - {me} - - )} + + + {isBegun && ( <> @@ -71,6 +57,7 @@ const UserMatchListItem: React.FC<{ )} + {!isFulfilled && } ); }; diff --git a/src/components/matches/matchesList/helper.tsx b/src/components/matches/matchesList/helper.tsx index 5fa1b5b..9449737 100644 --- a/src/components/matches/matchesList/helper.tsx +++ b/src/components/matches/matchesList/helper.tsx @@ -1,12 +1,17 @@ -import { MatchWithDetails } from "@boklisten/bl-model"; +import { KeyboardDoubleArrowRight, SwapHoriz } from "@mui/icons-material"; +import { SxProps, Typography } from "@mui/material"; +import { Box } from "@mui/system"; import { Properties } from "csstype"; +import React from "react"; -import { GroupedMatches } from "@/utils/types"; +import theme from "@/utils/theme"; +import { StandMatchWithDetails, UserMatchWithDetails } from "@/utils/types"; export const sectionStyle: Properties = { display: "flex", flexDirection: "column", gap: "1em", + marginTop: "1rem", }; export function formatActionsString(handoffItems: number, pickupItems: number) { @@ -43,65 +48,94 @@ export function formatActionsString(handoffItems: number, pickupItems: number) { return stringBuilder.join(""); } -/** - * Groups array of matches location. - * - * If the input-array is sorted by time, then the output will be as well - * (assuming for-of or other insertion-order iteration of keys). - * - * @param matches the matches to group - */ -export function groupMatchesByTimeAndLocation( - matches: T[], -): GroupedMatches { - const keyToData: Map = - new Map(); - const matchesByKey: Map = new Map(); - for (const match of matches) { - const date = match.meetingInfo.date - ? new Date(match.meetingInfo.date) - : null; - const key = (date?.getTime() ?? null) + match.meetingInfo.location; - const items = matchesByKey.get(key) ?? []; - keyToData.set(key, { - time: date?.getTime() ?? null, - location: match.meetingInfo.location, - }); - matchesByKey.set(key, items); - items.push(match); - } - return { matchesByKey, keyToData }; -} - -/** - * Sort groups by time ascending. - * - * @param groups the groups to sort - * @returns array of keys of groups - */ -export function getSortedMatchGroups( - groups: GroupedMatches, -): string[] { - const keys = [...groups.keyToData.keys()]; - keys.sort((a, b) => { - const timeA = groups.keyToData.get(a)!.time; - const timeB = groups.keyToData.get(b)!.time; - if (!timeA) { - return 1; - } - if (!timeB) { - return -1; - } - return new Date(timeA) >= new Date(timeB) ? 1 : -1; +export const FormattedDatetime = ({ date }: { date: Date }) => { + const dateString = date.toLocaleDateString("no", { + timeZone: "Europe/Oslo", + dateStyle: "long", }); - return keys; -} - -export function formatDatetime(date: Date): string { - const dateString = date.toLocaleDateString("no", { timeZone: "Europe/Oslo" }); const timeString = date.toLocaleTimeString("no", { timeZone: "Europe/Oslo", timeStyle: "short", }); - return `${dateString} ${timeString}`; + return ( + <> + {timeString} + , {dateString} + + ); +}; + +const me = Meg; + +interface UserMatchTitleProps { + match: UserMatchWithDetails; + isSender: boolean; } + +export const UserMatchTitle = ({ match, isSender }: UserMatchTitleProps) => { + const arrowSize: string = "1.18em"; + return ( + <> + {isSender ? ( + <> + {me}{" "} + {" "} + + {match.receiverDetails.name} + + + ) : ( + <> + + {match.senderDetails.name} + {" "} + {" "} + {me} + + )} + + ); +}; + +interface StandMatchTitleProps { + match: StandMatchWithDetails; +} + +export const StandMatchTitle = ({ match }: StandMatchTitleProps) => { + const hasHandoffItems = match.expectedHandoffItems.length > 0; + const hasPickupItems = match.expectedPickupItems.length > 0; + + const stand = ( + + Stand + + ); + + const isMeFirst = hasPickupItems ? hasHandoffItems : true; + + const iconStyle: SxProps = { + verticalAlign: "text-bottom", + fontSize: "1.18em", + }; + + const left = isMeFirst ? me : stand; + const right = isMeFirst ? stand : me; + const arrow = hasHandoffItems ? ( + hasPickupItems ? ( + + ) : ( + + ) + ) : ( + + ); + return ( + <> + {left} {arrow} {right} + + ); +}; diff --git a/src/utils/typographyVariants.ts b/src/utils/typographyVariants.ts index d247e0f..a25c4f7 100644 --- a/src/utils/typographyVariants.ts +++ b/src/utils/typographyVariants.ts @@ -4,19 +4,13 @@ */ import React from "react"; -// @ts-expect-error used for module JSdoc -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import theme from "@/utils/theme"; - declare module "@mui/material/styles" { - // eslint-disable-next-line no-unused-vars interface TypographyVariants { cardHeader: React.CSSProperties; title: React.CSSProperties; } // allow configuration using `createTheme` - // eslint-disable-next-line no-unused-vars interface TypographyVariantsOptions { cardHeader?: React.CSSProperties; title?: React.CSSProperties; @@ -25,7 +19,6 @@ declare module "@mui/material/styles" { // Update the Typography's variant prop options declare module "@mui/material/Typography" { - // eslint-disable-next-line no-unused-vars interface TypographyPropsVariantOverrides { cardHeader: true; title: true; diff --git a/tsconfig.json b/tsconfig.json index a5cbd61..a2dc08c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,6 @@ "checkJs": true, "skipLibCheck": true, "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, "exactOptionalPropertyTypes": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true,