Skip to content
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
2 changes: 1 addition & 1 deletion src/apiClient/getPagesCount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ListWrapper } from "./types";

export function getPagesCount(data: ListWrapper<unknown>): number {
return Math.ceil(data?.count / data.pageSize);
return Math.ceil(data?.count / (data.pageSize ?? 100));
}
3 changes: 2 additions & 1 deletion src/components/PersonSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { skipToken } from "@reduxjs/toolkit/query";
import { countBy } from "lodash";
import { useState } from "react";

Expand All @@ -20,7 +21,7 @@ export function PersonSelect({ value, onChange, className, invalid }: Props) {
const { personList, isFetching } = usePeopleList({
q: query,
});
const { data: selectedPerson } = useGetPersonQuery(value ?? "", {
const { data: selectedPerson } = useGetPersonQuery(value ?? skipToken, {
skip: value == null,
});

Expand Down
9 changes: 3 additions & 6 deletions src/pages/Approvals/AddNewApproval.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";

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

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

const dispatch = useDispatch();
const onSubmit = (args: CreateNewApprovalArgs) => {
createNewApproval(args)
.then(() => {
setError(undefined);
// TODO: We should use RTK's mutations instead of refetching everything
refetchAllApprovals({ past: false });
refetchAllApprovals({ past: undefined });
dispatch(gearDbApi.util.invalidateTags(["Approvals"]));
if (personId != null) {
refetchPersonApprovals({ personID: personId, past: false });
navigate(`/people/${personId}?tab=approvals`);
} else {
navigate("/approvals");
Expand Down
12 changes: 11 additions & 1 deletion src/pages/Approvals/AddNewApprovalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ export function AddNewApprovalForm({ onSubmit }: Props) {
const searchParams = new URLSearchParams(location.search);
const personId = searchParams.get("personId");

const today = dayjs().startOf("day").toDate();
const endDate = nextTuesday(today);
const formObject = useForm<FormValues>({
defaultValues: {
items: [defaultItem],
renter: personId || undefined,
renter: personId ?? undefined,
startDate: dayjs().startOf("day").toDate(),
endDate: endDate,
},
});

Expand Down Expand Up @@ -119,6 +122,13 @@ export function AddNewApprovalForm({ onSubmit }: Props) {
);
}

function nextTuesday(from: Date): Date {
const today = dayjs(from);
// Ensure a weekend has passed
const nextSunday = today.day(7);
return nextSunday.day(2).toDate();
}

function validateApproval(approval: FormValues): CreateNewApprovalArgs {
const { startDate, endDate, renter, items, note } = approval;
if (
Expand Down
9 changes: 8 additions & 1 deletion src/pages/People/CheckoutStaging.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { map, sum } from "lodash";
import { isEmpty, map, sum } from "lodash";

import type { Person } from "src/apiClient/people";
import { RemoveButton } from "src/components/Buttons";
import { GearLink } from "src/components/GearLink";
import { fmtAmount } from "src/lib/fmtNumber";

import { usePersonPageContext } from "./PeoplePage/PersonPageContext";
import { RestrictedGearWarning } from "./RestrictedGearWarning";

export function CheckoutStaging({ onCheckout }: { onCheckout: () => void }) {
const { person, checkoutBasket, isApproved } = usePersonPageContext();
const gearToCheckout = checkoutBasket.items;
const totalDeposit = sum(map(gearToCheckout, "depositAmount"));
const unaprovedRestrictedItems = checkoutBasket.items.filter(
({ id, type, restricted }) => restricted && !isApproved(id, type.id),
);

return (
<div className="border rounded-2 p-2 mb-3 bg-light">
<h3>Gear to check out</h3>
<hr />
{!isEmpty(unaprovedRestrictedItems) && (
<RestrictedGearWarning gear={unaprovedRestrictedItems} />
)}
<h5>
Deposit due:{" "}
{hasFFCheck(person) ? (
Expand Down
47 changes: 22 additions & 25 deletions src/pages/People/PeoplePage/PersonPageContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import dayjs from "dayjs";
import { flow, keyBy, map, mapValues, sum } from "lodash";
import React, { useCallback, useContext, useState } from "react";
import React, { useContext, useState } from "react";

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

const isApproved = useCallback(
(gearId: string, typeId: number) => {
return activeApprovals.some(({ items }) => {
return items.some((item) => {
if (item.type === "specificItem") {
return item.item.gearItem.id === gearId;
}
if (item.item.gearType.id !== typeId) {
return false;
}
const checkedOut = checkoutBasketBase.items.filter(
({ type }) => type.id === item.item.gearType.id,
);
if (checkedOut.length < item.item.quantity) {
return true;
}
return checkedOut
.slice(0, item.item.quantity)
.map((g) => g.id)
.includes(gearId);
});
const isApproved = (gearId: string, typeId: number) => {
return activeApprovals.some(({ items }) => {
return items.some((item) => {
if (item.type === "specificItem") {
return item.item.gearItem.id === gearId;
}
if (item.item.gearType.id !== typeId) {
return false;
}
const checkedOut = checkoutBasketBase.items.filter(
({ type }) => type.id === item.item.gearType.id,
);
if (checkedOut.length < item.item.quantity) {
return true;
}
return checkedOut
.slice(0, item.item.quantity)
.map((g) => g.id)
.includes(gearId);
});
},
[activeApprovals],
);
});
};

const calculatedTotalRentals = sum(
map(rentalsWithOverride, (item) => {
Expand Down
17 changes: 3 additions & 14 deletions src/pages/People/PersonPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useGetPersonQuery, useGetRenterApprovalsQuery } from "src/redux/api";

import { BuyGear } from "./BuyGear";
import { CheckoutStaging } from "./CheckoutStaging";
import { MoreGear } from "./MoreGear";
import {
PersonPageContextProvider,
usePersonPageContext,
Expand All @@ -18,6 +17,7 @@ import { PersonProfile } from "./PersonProfile";
import { PersonRentals } from "./PersonRentals";
import { PersonRentalsHistory } from "./PersonRentalsHistory";
import { PersonPageTabs, PersonTabsSelector, useTab } from "./PersonTabs";
import { RentGear } from "./RentGear";
import { ReturnStaging } from "./ReturnStaging";

export function PersonPage() {
Expand Down Expand Up @@ -69,18 +69,7 @@ function PersonPageInner() {
Please remind renter to return overdue gear!
</div>
)}
{!isEmpty(unaprovedRestrictedItems) && (
<div className="alert alert-warning" role="alert">
⚠️ <strong>Unapproved restricted gear</strong> in the basket:
<ul>
{unaprovedRestrictedItems.map((i) => (
<li key={i.id}>{i.id}</li>
))}
</ul>
Please ensure that the renter has been approved by the relevant
activity chair to check this gear out (i.e. via email)
</div>
)}

<div className="col-12 col-md-5 p-2">
<PersonProfile />
<Notes
Expand All @@ -106,7 +95,7 @@ function PersonPageInner() {
<div className="col-12 col-md-7 p-2">
<PersonTabsSelector activeTab={tab} updateTab={setTab} />
{tab === PersonPageTabs.gearOut && <PersonRentals />}
{tab === PersonPageTabs.rent && <MoreGear />}
{tab === PersonPageTabs.rent && <RentGear />}
{tab === PersonPageTabs.buy && <BuyGear />}
{tab === PersonPageTabs.approvals && <PersonApprovals />}
{tab === PersonPageTabs.history && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useGearList } from "src/redux/api";

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

export function MoreGear() {
export function RentGear() {
const { checkoutBasket, isApproved } = usePersonPageContext();
const [query, setQuery] = useState<string>("");
const [page, setPage] = useState<number>(1);
Expand Down
26 changes: 26 additions & 0 deletions src/pages/People/RestrictedGearWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { GearSummary } from "src/apiClient/gear";

export function RestrictedGearWarning({ gear }: { gear: GearSummary[] }) {
return (
<div className="alert alert-warning p-2">
<h4 className="text-center">
<strong>Unapproved restricted gear</strong>
</h4>
Some items are <strong>restricted</strong>, and are not marked as approved
in the database.
<br />
Please make sure renter has received approval (e.g. over email), or is a
leader running a MITOC trip.
<br />
<br />
Items:
<ul>
{gear.map(({ type, id }) => (
<li key={id}>
{id} ({type.typeName})
</li>
))}
</ul>
</div>
);
}
4 changes: 3 additions & 1 deletion src/redux/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const gearDbApi = createApi({
paramsSerializer: (params) =>
queryString.stringify(params, { arrayFormat: "none" }),
}),
tagTypes: ["Approvals"],
endpoints: (builder) => ({
getConfig: builder.query<Config, void>({
query: () => `config/`,
Expand Down Expand Up @@ -152,8 +153,8 @@ export const gearDbApi = createApi({
past,
},
}),
providesTags: ["Approvals"],
}),

getRenterApprovals: builder.query<
ListWrapper<RenterApproval>,
{ personID: string; past: boolean; future?: boolean }
Expand All @@ -164,6 +165,7 @@ export const gearDbApi = createApi({
past,
},
}),
providesTags: ["Approvals"],
}),
getGearLocations: builder.query<GearLocation[], void>({
query: () => "/gear-locations/",
Expand Down
Loading