Skip to content
This repository has been archived by the owner on Dec 18, 2024. It is now read-only.

Commit

Permalink
feat(rapid-handout): barebones solution
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrianAndersen committed Aug 19, 2024
1 parent 50da90b commit 3d53a7e
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/app/admin/hurtigutdeling/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Button, Card, Container, Typography } from "@mui/material";
import { Box } from "@mui/system";
import { Metadata } from "next";

import DynamicLink from "@/components/DynamicLink";
import RapidHandout from "@/components/RapidHandout";
import BL_CONFIG from "@/utils/bl-config";

export const metadata: Metadata = {
title: "Hurtigutdeling",
description: "Adminverktøy for å raskt dele ut bøker.",
};

export default function HandoutPage() {
return (
<Card sx={{ paddingBottom: 4 }}>
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 2,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography variant="h1">Hurtigutdeling</Typography>
<DynamicLink href={BL_CONFIG.blAdmin.basePath}>
<Button sx={{ mt: 2 }}>tilbake til bl-admin</Button>
</DynamicLink>
<RapidHandout />
</Box>
</Container>
</Card>
);
}
37 changes: 37 additions & 0 deletions src/components/RapidHandout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";
import { UserDetail } from "@boklisten/bl-model";
import { Alert, Button } from "@mui/material";
import { useState } from "react";

import { isLoggedIn } from "@/api/auth";
import DynamicLink from "@/components/DynamicLink";
import RapidHandoutDetails from "@/components/RapidHandoutDetails";
import UserDetailSearchField from "@/components/search/UserDetailSearchField";
import useIsHydrated from "@/utils/useIsHydrated";

export default function RapidHandout() {
const hydrated = useIsHydrated();
const [customer, setCustomer] = useState<UserDetail | null>(null);

return hydrated && isLoggedIn() ? (
<>
<UserDetailSearchField
onSelectedResult={(userDetail) => {
setCustomer(userDetail);
}}
/>
{customer && <RapidHandoutDetails customer={customer} />}
</>
) : (
<>
<Alert sx={{ mt: 2 }} severity="info">
Du må logge inn som administrator for å bruke denne siden.
</Alert>
<DynamicLink href={"/auth/login?redirect=admin/hurtigutdeling"}>
<Button variant={"contained"} sx={{ mt: 2 }}>
Logg inn
</Button>
</DynamicLink>
</>
);
}
53 changes: 53 additions & 0 deletions src/components/RapidHandoutDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Order, UserDetail } from "@boklisten/bl-model";
import { Alert, Typography } from "@mui/material";
import useSWR from "swr";

import BlFetcher from "@/api/blFetcher";
import { ItemStatus } from "@/components/matches/matches-helper";
import MatchItemTable from "@/components/matches/MatchItemTable";
import BL_CONFIG from "@/utils/bl-config";

function mapOrdersToItemStatuses(orders: Order[]): ItemStatus[] {
return orders
.filter((order) => order.byCustomer && !order.handoutByDelivery)
.flatMap((order) => order.orderItems)
.filter(
(orderItem) =>
!orderItem.movedToOrder &&
!orderItem.handout &&
(orderItem.type === "rent" ||
orderItem.type === "buy" ||
orderItem.type === "partly-payment"),
)
.map((oi) => ({
id: oi.item,
title: oi.title,
fulfilled: false,
}));
}

export default function RapidHandoutDetails({
customer,
}: {
customer: UserDetail;
}) {
const { data: orders } = useSWR(
`${BL_CONFIG.collection.order}?placed=true&customer=${customer.id}`,
BlFetcher.get<Order[]>,
{ refreshInterval: 5000 },
);
const itemStatuses = mapOrdersToItemStatuses(orders ?? []);

return itemStatuses.length === 0 ? (
<Alert severity={"info"} sx={{ mt: 2 }}>
Denne kunden har for øyeblikket ingen bestilte bøker
</Alert>
) : (
<>
<Typography variant={"h2"} textAlign={"center"} mt={6}>
Plukkliste
</Typography>
<MatchItemTable itemStatuses={itemStatuses} isSender={true} />
</>
);
}
76 changes: 76 additions & 0 deletions src/components/search/UserDetailSearchField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { UserDetail } from "@boklisten/bl-model";
import { Autocomplete, Typography } from "@mui/material";
import TextField from "@mui/material/TextField";
import { Box } from "@mui/system";
import { useState } from "react";

import BlFetcher from "@/api/blFetcher";
import UserDetailSearchResult from "@/components/search/UserDetailSearchResult";
import BL_CONFIG from "@/utils/bl-config";

export default function UserDetailSearchField({
onSelectedResult,
}: {
onSelectedResult: (userDetail: UserDetail | null) => void;
}) {
const [searchValue, setSearchValue] = useState<UserDetail | null>(null);
const [searchResults, setSearchResults] = useState<UserDetail[]>([]);
return (
<Box sx={{ width: "100%" }}>
<Typography sx={{ mt: 2, mb: 1, textAlign: "center" }}>
Søk etter en kunde for å se plukkliste
</Typography>
<Autocomplete
autoComplete
value={searchValue}
isOptionEqualToValue={(a, b) => a.id === b.id}
filterSelectedOptions
getOptionLabel={(option) => option.name ?? option.email}
getOptionKey={(option) => option.id}
filterOptions={(x) => x}
noOptionsText={null}
onInputChange={async (event, newInputValue) => {
if (event === null) return;
if (newInputValue.length < 3) {
onSelectedResult(null);
setSearchValue(null);
setSearchResults([]);
return;
}
try {
const result = await BlFetcher.get<UserDetail[]>(
`${BL_CONFIG.collection.userDetail}?s=${newInputValue}`,
);
setSearchResults(result);
} catch {
setSearchResults([]);
}
}}
options={searchResults}
renderOption={({ key }, userDetail) => (
<UserDetailSearchResult
key={key}
userDetail={userDetail}
onClick={() => {
setSearchValue(userDetail);
setSearchResults([userDetail]);
onSelectedResult(userDetail);
}}
/>
)}
renderInput={(params) => (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error Using example from https://mui.com/material-ui/react-autocomplete/#system-FreeSolo.tsx
<TextField
{...params}
label="Søk etter kunde"
InputProps={{
...params.InputProps,
type: "search",
}}
/>
)}
/>
</Box>
);
}
30 changes: 30 additions & 0 deletions src/components/search/UserDetailSearchResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { UserDetail } from "@boklisten/bl-model";
import { Email, Person, Phone } from "@mui/icons-material";
import { ListItemButton, Stack, Typography } from "@mui/material";

export default function UserDetailSearchResult({
userDetail,
onClick,
}: {
userDetail: UserDetail;
onClick: () => void;
}) {
return (
<ListItemButton onClick={onClick}>
<Stack gap={0.5}>
<Stack gap={0.5} direction={"row"} alignItems={"center"}>
<Person />
<Typography>{userDetail.name}</Typography>
</Stack>
<Stack gap={0.5} direction={"row"} alignItems={"center"}>
<Email />
<Typography>{userDetail.email}</Typography>
</Stack>
<Stack gap={0.5} direction={"row"} alignItems={"center"}>
<Phone />
<Typography>{userDetail.phone}</Typography>
</Stack>
</Stack>
</ListItemButton>
);
}

0 comments on commit 3d53a7e

Please sign in to comment.