Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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: 2 additions & 0 deletions src/apiClient/approvals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type Approval = {
items: ApprovalItem[];
};

export type RenterApproval = Omit<Approval, "renter">;

interface GearItem {
id: string;
type: GearTypeWithShorthand;
Expand Down
11 changes: 11 additions & 0 deletions src/components/AddApprovalLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Link } from "react-router-dom";

export function AddApprovalLink({ personId }: { personId?: string }) {
const to =
personId == null ? "/add-approval" : `/add-approval?personId=${personId}`;
return (
<Link to={to}>
<button className="btn btn-outline-primary mb-3">+ Add approval</button>
</Link>
);
}
31 changes: 31 additions & 0 deletions src/components/ApprovalItemsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApprovalItem } from "apiClient/approvals";
import { GearLink } from "components/GearLink";

/**
* Renders a list of approval items (gear types or specific gear items)
*/
export function ApprovalItemsList({ items }: { items: ApprovalItem[] }) {
return (
<ul className="list-unstyled">
{items.map((approvalItem) => {
if (approvalItem.type === "gearType") {
return (
<li key={`${approvalItem.item.gearType.id}-type`}>
{approvalItem.item.gearType.typeName} (
{approvalItem.item.gearType.shorthand}){" "}
<strong>x{approvalItem.item.quantity}</strong>
</li>
);
}
return (
<li key={`${approvalItem.item.gearItem.id}-item`}>
{approvalItem.item.gearItem.type.typeName} -{" "}
<GearLink id={approvalItem.item.gearItem.id}>
{approvalItem.item.gearItem.id}
</GearLink>
</li>
);
})}
</ul>
);
}
19 changes: 16 additions & 3 deletions src/components/PersonSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PersonSummary } from "apiClient/people";
import { countBy } from "lodash";
import { useState } from "react";

import { usePeopleList } from "redux/api";
import { usePeopleList, useGetPersonQuery } from "redux/api";

import { Select } from "./Select";
import { useDebounce } from "./useDebounce";
Expand All @@ -20,14 +20,26 @@ export function PersonSelect({ value, onChange, className, invalid }: Props) {
const { personList, isFetching } = usePeopleList({
q: query,
});
const { data: selectedPerson } = useGetPersonQuery(value ?? "", {
skip: value == null,
});

const getName = (person: PersonSummary) =>
`${person.firstName} ${person.lastName}`;

const namesCount = countBy(personList, getName);

const listWithSelection =
personList == null
? []
: selectedPerson == null
? personList
: personList.some((p) => p.id === value)
? personList
: [...personList, selectedPerson];

const options =
personList?.map((person) => {
listWithSelection?.map((person) => {
const name = getName(person);
const fullName =
namesCount[name] > 1 ? `${name} (${person.email})` : name;
Expand All @@ -38,7 +50,8 @@ export function PersonSelect({ value, onChange, className, invalid }: Props) {
};
}) ?? [];

const selectedOption = options.find((o) => o.id === value);
// We want == rather than ===, there is some type mess here
const selectedOption = options.find((o) => o.id == value);

return (
<Select
Expand Down
2 changes: 1 addition & 1 deletion src/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const newApprovalUI = false;
export const newApprovalUI = true;
47 changes: 17 additions & 30 deletions src/pages/Approvals/AddNewApproval.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { useHistory, useLocation } from "react-router-dom";

import { createNewApproval, CreateNewApprovalArgs } from "apiClient/approvals";
import { APIError as APIErrorClass } from "apiClient/client";
Expand All @@ -11,22 +11,31 @@ import { AddNewApprovalForm } from "./AddNewApprovalForm";

export function AddNewApproval() {
useSetPageTitle("Approve restricted gear rental");
const [success, setSuccess] = useState<boolean>(false);
const history = useHistory();
const location = useLocation();
const [error, setError] = useState<APIErrorType | undefined>();
const refetchApprovals = gearDbApi.useLazyGetApprovalsQuery()[0];
const refetchAllApprovals = gearDbApi.useLazyGetApprovalsQuery()[0];
const refetchPersonApprovals = gearDbApi.useLazyGetRenterApprovalsQuery()[0];

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

const onSubmit = (args: CreateNewApprovalArgs) => {
createNewApproval(args)
.then(() => {
setError(undefined);
setSuccess(true);
// TODO: We should use RTK's mutations instead of refetching everything
refetchApprovals({ past: false });
refetchApprovals({ past: undefined });
refetchAllApprovals({ past: false });
refetchAllApprovals({ past: undefined });
if (personId != null) {
refetchPersonApprovals({ personID: personId, past: false });
history.push(`/people/${personId}?tab=approvals`);
} else {
history.push("/approvals");
}
})
.catch((err) => {
if (err instanceof APIErrorClass) {
setSuccess(false);
setError(err.error);
}
throw err;
Expand All @@ -40,29 +49,7 @@ export function AddNewApproval() {
{error && (
<div className="alert alert-danger">Approval failed: {error.msg}</div>
)}
{!success && <AddNewApprovalForm onSubmit={onSubmit} />}
{success && (
<>
<p className="mb-2 alert alert-success">
Success! The approval was granted:
</p>

<div className="d-flex justify-content-between">
<Link to="/approvals">
<button type="button" className="btn btn-outline-secondary">
Go to approvals list
</button>
</Link>
<button
type="button"
className="btn btn-primary"
onClick={() => setSuccess(false)}
>
Approve another rental
</button>
</div>
</>
)}
<AddNewApprovalForm onSubmit={onSubmit} />
</div>
</div>
);
Expand Down
9 changes: 8 additions & 1 deletion src/pages/Approvals/AddNewApprovalForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dayjs from "dayjs";
import DatePicker from "react-datepicker";
import { useForm } from "react-hook-form";
import { useHistory } from "react-router-dom";
import { useHistory, useLocation } from "react-router-dom";

import {
ApprovalItemToCreate,
Expand All @@ -22,9 +23,15 @@ const LabeledInput = makeLabeledInput<FormValues>();

export function AddNewApprovalForm({ onSubmit }: Props) {
const history = useHistory();
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const personId = searchParams.get("personId");

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

Expand Down
10 changes: 2 additions & 8 deletions src/pages/Approvals/ApprovalsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { Link } from "react-router-dom";

import { AddApprovalLink } from "components/AddApprovalLink";
import { Checkbox } from "components/Inputs/Checkbox";
import { TablePagination } from "components/TablePagination";
import { useSetPageTitle } from "hooks";
Expand Down Expand Up @@ -40,13 +40,7 @@ export function ApprovalsPage() {
}

<div className="col-md d-flex flex-grow-1 justify-content-end">
{isApprover && (
<Link to="/add-approval">
<button className="btn btn-outline-primary mb-3">
+ Add approval
</button>
</Link>
)}
{isApprover && <AddApprovalLink />}
</div>
</div>
)}
Expand Down
37 changes: 9 additions & 28 deletions src/pages/Approvals/ApprovalsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useMemo } from "react";

import { Approval, deleteApproval } from "apiClient/approvals";
import { PersonBase } from "apiClient/people";
import { ApprovalItemsList } from "components/ApprovalItemsList";
import { DataGrid } from "components/DataGrid";
import { GearLink } from "components/GearLink";
import { PersonLink } from "components/PersonLink";
import { formatDate } from "lib/fmtDate";
import { PersonBase } from "apiClient/people";
import { PersonLink } from "components/PersonLink";

export function ApprovalsTable({
approvals,
Expand Down Expand Up @@ -63,40 +63,21 @@ function EndDateCell({ item: approval }: { item: Approval }) {
}

function ItemsCell({ item: approval }: { item: Approval }) {
return (
<ul>
{approval.items.map(({ type, item }) => {
if (type === "gearType") {
return (
<li key={item.gearType.id}>
{item.gearType.typeName} ({item.gearType.shorthand}) -
{item.quantity} {item.quantity > 1 ? "items" : "item"}
</li>
);
}
return (
<li key={item.gearItem.id}>
{item.gearItem.type.typeName} -{" "}
<GearLink id={item.gearItem.id}>{item.gearItem.id}</GearLink>{" "}
</li>
);
})}
</ul>
);
return <ApprovalItemsList items={approval.items} />;
}

function RenterCell({ item: approval }: { item: Approval }) {
return <PersonCell value={approval.renter} />;
return <PersonCell person={approval.renter} />;
}

function ApproverCell({ item: approval }: { item: Approval }) {
return <PersonCell value={approval.approvedBy} />;
return <PersonCell person={approval.approvedBy} />;
}

function PersonCell({ value }: { value: PersonBase }) {
function PersonCell({ person }: { person: PersonBase }) {
return (
<PersonLink id={String(value.id)}>
{value.firstName} {value.lastName}
<PersonLink id={String(person.id)}>
{person.firstName} {person.lastName}
</PersonLink>
);
}
Expand Down
10 changes: 8 additions & 2 deletions src/pages/People/CheckoutStaging.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fmtAmount } from "lib/fmtNumber";
import { usePersonPageContext } from "./PeoplePage/PersonPageContext";

export function CheckoutStaging({ onCheckout }: { onCheckout: () => void }) {
const { person, checkoutBasket } = usePersonPageContext();
const { person, checkoutBasket, isApproved } = usePersonPageContext();
const gearToCheckout = checkoutBasket.items;
const totalDeposit = sum(map(gearToCheckout, "depositAmount"));

Expand Down Expand Up @@ -57,7 +57,13 @@ export function CheckoutStaging({ onCheckout }: { onCheckout: () => void }) {
{restricted && (
<>
<br />
<strong className="text-warning">RESTRICTED</strong>
{!isApproved(id, type.id) ? (
<strong className="text-warning">RESTRICTED</strong>
) : (
<strong className="text-success">
☑ Approved
</strong>
)}
</>
)}
</td>
Expand Down
12 changes: 9 additions & 3 deletions src/pages/People/MoreGear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useGearList } from "redux/api";
import { usePersonPageContext } from "./PeoplePage/PersonPageContext";

export function MoreGear() {
const { checkoutBasket } = usePersonPageContext();
const { checkoutBasket, isApproved } = usePersonPageContext();
const [query, setQuery] = useState<string>("");
const [page, setPage] = useState<number>(1);

Expand All @@ -19,7 +19,7 @@ export function MoreGear() {
return (
<StyledDiv className="border rounded-2 p-2 bg-light">
<div className="d-flex justify-content-between">
<h3 className="mb-4">More gear</h3>
<h3 className="mb-4">Rent gear</h3>
{query && nbPages != null && nbPages > 1 && (
<TablePagination setPage={setPage} page={page} nbPage={nbPages} />
)}
Expand Down Expand Up @@ -98,7 +98,13 @@ export function MoreGear() {
{restricted && (
<>
<br />
<strong className="text-warning">RESTRICTED</strong>
{!isApproved(id, type.id) ? (
<strong className="text-warning">RESTRICTED</strong>
) : (
<strong className="text-success">
☑ Approved
</strong>
)}
</>
)}
</td>
Expand Down
Loading
Loading