Skip to content

Commit 458589d

Browse files
authored
Small improvements of approvals (#102)
* Use RTK cache invalidation * Add default for end of approval in form * Rename MoreGear component * Use skipToken * Remove ineffective memoization * Fix broken pagination * Use better warning
1 parent 206fafb commit 458589d

File tree

10 files changed

+80
-51
lines changed

10 files changed

+80
-51
lines changed

src/apiClient/getPagesCount.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ListWrapper } from "./types";
22

33
export function getPagesCount(data: ListWrapper<unknown>): number {
4-
return Math.ceil(data?.count / data.pageSize);
4+
return Math.ceil(data?.count / (data.pageSize ?? 100));
55
}

src/components/PersonSelect.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { skipToken } from "@reduxjs/toolkit/query";
12
import { countBy } from "lodash";
23
import { useState } from "react";
34

@@ -20,7 +21,7 @@ export function PersonSelect({ value, onChange, className, invalid }: Props) {
2021
const { personList, isFetching } = usePeopleList({
2122
q: query,
2223
});
23-
const { data: selectedPerson } = useGetPersonQuery(value ?? "", {
24+
const { data: selectedPerson } = useGetPersonQuery(value ?? skipToken, {
2425
skip: value == null,
2526
});
2627

src/pages/Approvals/AddNewApproval.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState } from "react";
2+
import { useDispatch } from "react-redux";
23
import { useLocation, useNavigate } from "react-router-dom";
34

45
import {
@@ -17,21 +18,17 @@ export default function AddNewApproval() {
1718
const navigate = useNavigate();
1819
const location = useLocation();
1920
const [error, setError] = useState<APIErrorType | undefined>();
20-
const refetchAllApprovals = gearDbApi.useLazyGetApprovalsQuery()[0];
21-
const refetchPersonApprovals = gearDbApi.useLazyGetRenterApprovalsQuery()[0];
2221

2322
const searchParams = new URLSearchParams(location.search);
2423
const personId = searchParams.get("personId");
2524

25+
const dispatch = useDispatch();
2626
const onSubmit = (args: CreateNewApprovalArgs) => {
2727
createNewApproval(args)
2828
.then(() => {
2929
setError(undefined);
30-
// TODO: We should use RTK's mutations instead of refetching everything
31-
refetchAllApprovals({ past: false });
32-
refetchAllApprovals({ past: undefined });
30+
dispatch(gearDbApi.util.invalidateTags(["Approvals"]));
3331
if (personId != null) {
34-
refetchPersonApprovals({ personID: personId, past: false });
3532
navigate(`/people/${personId}?tab=approvals`);
3633
} else {
3734
navigate("/approvals");

src/pages/Approvals/AddNewApprovalForm.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ export function AddNewApprovalForm({ onSubmit }: Props) {
2727
const searchParams = new URLSearchParams(location.search);
2828
const personId = searchParams.get("personId");
2929

30+
const today = dayjs().startOf("day").toDate();
31+
const endDate = nextTuesday(today);
3032
const formObject = useForm<FormValues>({
3133
defaultValues: {
3234
items: [defaultItem],
33-
renter: personId || undefined,
35+
renter: personId ?? undefined,
3436
startDate: dayjs().startOf("day").toDate(),
37+
endDate: endDate,
3538
},
3639
});
3740

@@ -119,6 +122,13 @@ export function AddNewApprovalForm({ onSubmit }: Props) {
119122
);
120123
}
121124

125+
function nextTuesday(from: Date): Date {
126+
const today = dayjs(from);
127+
// Ensure a weekend has passed
128+
const nextSunday = today.day(7);
129+
return nextSunday.day(2).toDate();
130+
}
131+
122132
function validateApproval(approval: FormValues): CreateNewApprovalArgs {
123133
const { startDate, endDate, renter, items, note } = approval;
124134
if (

src/pages/People/CheckoutStaging.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
import { map, sum } from "lodash";
1+
import { isEmpty, map, sum } from "lodash";
22

33
import type { Person } from "src/apiClient/people";
44
import { RemoveButton } from "src/components/Buttons";
55
import { GearLink } from "src/components/GearLink";
66
import { fmtAmount } from "src/lib/fmtNumber";
77

88
import { usePersonPageContext } from "./PeoplePage/PersonPageContext";
9+
import { RestrictedGearWarning } from "./RestrictedGearWarning";
910

1011
export function CheckoutStaging({ onCheckout }: { onCheckout: () => void }) {
1112
const { person, checkoutBasket, isApproved } = usePersonPageContext();
1213
const gearToCheckout = checkoutBasket.items;
1314
const totalDeposit = sum(map(gearToCheckout, "depositAmount"));
15+
const unaprovedRestrictedItems = checkoutBasket.items.filter(
16+
({ id, type, restricted }) => restricted && !isApproved(id, type.id),
17+
);
1418

1519
return (
1620
<div className="border rounded-2 p-2 mb-3 bg-light">
1721
<h3>Gear to check out</h3>
1822
<hr />
23+
{!isEmpty(unaprovedRestrictedItems) && (
24+
<RestrictedGearWarning gear={unaprovedRestrictedItems} />
25+
)}
1926
<h5>
2027
Deposit due:{" "}
2128
{hasFFCheck(person) ? (

src/pages/People/PeoplePage/PersonPageContext.tsx

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dayjs from "dayjs";
22
import { flow, keyBy, map, mapValues, sum } from "lodash";
3-
import React, { useCallback, useContext, useState } from "react";
3+
import React, { useContext, useState } from "react";
44

55
import { RenterApproval } from "src/apiClient/approvals";
66
import { GearSummary } from "src/apiClient/gear";
@@ -72,31 +72,28 @@ function useMakePersonPageContext({ person, refreshPerson, approvals }: Props) {
7272
return startDate.isSameOrBefore(today) && endDate.isSameOrAfter(today);
7373
});
7474

75-
const isApproved = useCallback(
76-
(gearId: string, typeId: number) => {
77-
return activeApprovals.some(({ items }) => {
78-
return items.some((item) => {
79-
if (item.type === "specificItem") {
80-
return item.item.gearItem.id === gearId;
81-
}
82-
if (item.item.gearType.id !== typeId) {
83-
return false;
84-
}
85-
const checkedOut = checkoutBasketBase.items.filter(
86-
({ type }) => type.id === item.item.gearType.id,
87-
);
88-
if (checkedOut.length < item.item.quantity) {
89-
return true;
90-
}
91-
return checkedOut
92-
.slice(0, item.item.quantity)
93-
.map((g) => g.id)
94-
.includes(gearId);
95-
});
75+
const isApproved = (gearId: string, typeId: number) => {
76+
return activeApprovals.some(({ items }) => {
77+
return items.some((item) => {
78+
if (item.type === "specificItem") {
79+
return item.item.gearItem.id === gearId;
80+
}
81+
if (item.item.gearType.id !== typeId) {
82+
return false;
83+
}
84+
const checkedOut = checkoutBasketBase.items.filter(
85+
({ type }) => type.id === item.item.gearType.id,
86+
);
87+
if (checkedOut.length < item.item.quantity) {
88+
return true;
89+
}
90+
return checkedOut
91+
.slice(0, item.item.quantity)
92+
.map((g) => g.id)
93+
.includes(gearId);
9694
});
97-
},
98-
[activeApprovals],
99-
);
95+
});
96+
};
10097

10198
const calculatedTotalRentals = sum(
10299
map(rentalsWithOverride, (item) => {

src/pages/People/PersonPage.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { useGetPersonQuery, useGetRenterApprovalsQuery } from "src/redux/api";
88

99
import { BuyGear } from "./BuyGear";
1010
import { CheckoutStaging } from "./CheckoutStaging";
11-
import { MoreGear } from "./MoreGear";
1211
import {
1312
PersonPageContextProvider,
1413
usePersonPageContext,
@@ -18,6 +17,7 @@ import { PersonProfile } from "./PersonProfile";
1817
import { PersonRentals } from "./PersonRentals";
1918
import { PersonRentalsHistory } from "./PersonRentalsHistory";
2019
import { PersonPageTabs, PersonTabsSelector, useTab } from "./PersonTabs";
20+
import { RentGear } from "./RentGear";
2121
import { ReturnStaging } from "./ReturnStaging";
2222

2323
export function PersonPage() {
@@ -69,18 +69,7 @@ function PersonPageInner() {
6969
Please remind renter to return overdue gear!
7070
</div>
7171
)}
72-
{!isEmpty(unaprovedRestrictedItems) && (
73-
<div className="alert alert-warning" role="alert">
74-
⚠️ <strong>Unapproved restricted gear</strong> in the basket:
75-
<ul>
76-
{unaprovedRestrictedItems.map((i) => (
77-
<li key={i.id}>{i.id}</li>
78-
))}
79-
</ul>
80-
Please ensure that the renter has been approved by the relevant
81-
activity chair to check this gear out (i.e. via email)
82-
</div>
83-
)}
72+
8473
<div className="col-12 col-md-5 p-2">
8574
<PersonProfile />
8675
<Notes
@@ -106,7 +95,7 @@ function PersonPageInner() {
10695
<div className="col-12 col-md-7 p-2">
10796
<PersonTabsSelector activeTab={tab} updateTab={setTab} />
10897
{tab === PersonPageTabs.gearOut && <PersonRentals />}
109-
{tab === PersonPageTabs.rent && <MoreGear />}
98+
{tab === PersonPageTabs.rent && <RentGear />}
11099
{tab === PersonPageTabs.buy && <BuyGear />}
111100
{tab === PersonPageTabs.approvals && <PersonApprovals />}
112101
{tab === PersonPageTabs.history && (
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useGearList } from "src/redux/api";
99

1010
import { usePersonPageContext } from "./PeoplePage/PersonPageContext";
1111

12-
export function MoreGear() {
12+
export function RentGear() {
1313
const { checkoutBasket, isApproved } = usePersonPageContext();
1414
const [query, setQuery] = useState<string>("");
1515
const [page, setPage] = useState<number>(1);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { GearSummary } from "src/apiClient/gear";
2+
3+
export function RestrictedGearWarning({ gear }: { gear: GearSummary[] }) {
4+
return (
5+
<div className="alert alert-warning p-2">
6+
<h4 className="text-center">
7+
<strong>Unapproved restricted gear</strong>
8+
</h4>
9+
Some items are <strong>restricted</strong>, and are not marked as approved
10+
in the database.
11+
<br />
12+
Please make sure renter has received approval (e.g. over email), or is a
13+
leader running a MITOC trip.
14+
<br />
15+
<br />
16+
Items:
17+
<ul>
18+
{gear.map(({ type, id }) => (
19+
<li key={id}>
20+
{id} ({type.typeName})
21+
</li>
22+
))}
23+
</ul>
24+
</div>
25+
);
26+
}

src/redux/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const gearDbApi = createApi({
3030
paramsSerializer: (params) =>
3131
queryString.stringify(params, { arrayFormat: "none" }),
3232
}),
33+
tagTypes: ["Approvals"],
3334
endpoints: (builder) => ({
3435
getConfig: builder.query<Config, void>({
3536
query: () => `config/`,
@@ -152,8 +153,8 @@ export const gearDbApi = createApi({
152153
past,
153154
},
154155
}),
156+
providesTags: ["Approvals"],
155157
}),
156-
157158
getRenterApprovals: builder.query<
158159
ListWrapper<RenterApproval>,
159160
{ personID: string; past: boolean; future?: boolean }
@@ -164,6 +165,7 @@ export const gearDbApi = createApi({
164165
past,
165166
},
166167
}),
168+
providesTags: ["Approvals"],
167169
}),
168170
getGearLocations: builder.query<GearLocation[], void>({
169171
query: () => "/gear-locations/",

0 commit comments

Comments
 (0)