Skip to content

Commit

Permalink
Merge pull request #816 from Shelf-nu/bug-default-custodian-on-bookin…
Browse files Browse the repository at this point in the history
…g-page

fixing custodian select on booking page
  • Loading branch information
DonKoko authored Mar 5, 2024
2 parents 1194c2a + 3f3616e commit e3e4539
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 70 deletions.
6 changes: 3 additions & 3 deletions app/components/booking/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { BookingWithCustodians } from "~/routes/_layout+/bookings";
import { isFormProcessing, tw } from "~/utils";
import type { BookingFormData } from ".";
import { ActionsDropdown } from "./actions-dropdown";
import CustodianSelect from "../custody/custodian-select";
import CustodianUserSelect from "../custody/custodian-user-select";
import FormRow from "../forms/form-row";
import Input from "../forms/input";
import { Button } from "../shared";
Expand Down Expand Up @@ -276,8 +276,8 @@ export function BookingForm({
<label className="mb-2.5 block font-medium text-gray-700">
<span className="required-input-label">Custodian</span>
</label>
<CustodianSelect
defaultTeamMemberId={custodianUserId}
<CustodianUserSelect
defaultUserId={custodianUserId}
disabled={inputFieldIsDisabled}
className={
isSelfService
Expand Down
120 changes: 120 additions & 0 deletions app/components/custody/custodian-user-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useLoaderData } from "@remix-run/react";
import type { loader } from "~/routes/_layout+/assets.$assetId.give-custody";
import { tw } from "~/utils";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "../forms";
import { UserIcon } from "../icons";
import { Button } from "../shared";

/** Custodian select that works only for with users and doesnt support team members
* This is used for something like bookings where the custodian can only be a team member
*/
export default function CustodianUserSelect(
{
defaultUserId,
disabled,
showEmail,
className,
}: {
defaultUserId?: string;
disabled?: boolean;
showEmail?: boolean;
className?: string;
} = {
disabled: false,
showEmail: false,
className: "",
}
) {
const { teamMembers } = useLoaderData<typeof loader>();

// In the case of team member id passed, we set that to id and find the rest in the teamMembers array
let defaultValue = JSON.stringify({
id: teamMembers.find((member) => member.userId === defaultUserId)?.id,
name: teamMembers.find((member) => member.userId === defaultUserId)?.name,
userId: defaultUserId,
});
return (
<div className="relative w-full">
<Select name="custodian" defaultValue={defaultValue} disabled={disabled}>
<SelectTrigger
className={tw(
disabled ? "cursor-not-allowed" : "",
"custodian-selector text-left",
className
)}
>
<SelectValue placeholder="Select a team member" />
</SelectTrigger>
<div>
<SelectContent
className="w-[352px]"
position="popper"
align="start"
ref={(ref) =>
ref?.addEventListener("touchend", (e) => e.preventDefault())
}
>
{teamMembers.length > 0 ? (
<div className=" max-h-[320px] overflow-auto">
{teamMembers.map((member) => (
<SelectItem
key={member.id}
value={`${JSON.stringify({
id: member.id,
name: member.name,
userId: member?.userId,
})}`}
className="py-3"
>
{member.user ? (
<div className="flex items-center gap-3 truncate pr-1">
<img
src={
member.user.profilePicture ||
"/static/images/default_pfp.jpg"
}
className={"w-[20px] rounded-[4px]"}
alt={`${member.user.firstName} ${member.user.lastName}'s profile`}
/>
<span className=" whitespace-nowrap text-left font-medium text-gray-900">
{member.user.firstName} {member.user.lastName}
</span>
{showEmail ? (
<span className="truncate text-xs text-gray-500">
{member.user.email}
</span>
) : null}
</div>
) : (
<div className="flex items-center gap-3">
<i>
<UserIcon />
</i>
<span className=" flex-1 font-medium text-gray-900">
{member.name}
</span>
</div>
)}
</SelectItem>
))}
</div>
) : (
<div>
No team members found.{" "}
<Button to={"/settings/workspace"} variant="link">
Create team members
</Button>
</div>
)}
</SelectContent>
</div>
</Select>
</div>
);
}
146 changes: 79 additions & 67 deletions app/routes/_layout+/bookings.$bookingId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,81 +55,93 @@ export async function loader({ context, request, params }: LoaderFunctionArgs) {
});

const isSelfService = role === OrganizationRoles.SELF_SERVICE;

const bookingId = getRequiredParam(params, "bookingId");
const user = await getUserByID(authSession.userId);

const teamMembers = await db.teamMember.findMany({
where: {
deletedAt: null,
organizationId,
userId: {
not: null,
},
},
include: {
user: true,
},
orderBy: {
userId: "asc",
},
});

/** We create a teamMember entry to represent the org owner.
* Most important thing is passing the ID of the owner as the userId as we are currently only supporting
* assigning custody to users, not NRM.
*/
teamMembers.push({
id: "owner",
name: "owner",
user: user,
userId: user?.id as string,
organizationId,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
const booking = await getBooking({
id: getRequiredParam(params, "bookingId"),
});

const booking = await getBooking({ id: bookingId });
if (!booking) {
throw new ShelfStackError({ message: "Booking not found", status: 404 });
}

/**
* We need to do this in a separate query because we need to filter the bookings within an asset based on the booking.from and booking.to
* That way we know if the asset is available or not because we can see if they are booked for the same period
*/
const assets = await db.asset.findMany({
where: {
id: {
in: booking?.assets.map((a) => a.id) || [],
const [teamMembers, org, assets] = await db.$transaction([
/**
* We need to fetch the team members to be able to display them in the custodian dropdown.
*/
db.teamMember.findMany({
where: {
deletedAt: null,
organizationId,
userId: {
not: null,
},
},
},
include: {
category: true,
custody: true,
bookings: {
where: {
// id: { not: booking.id },
...(booking.from && booking.to
? {
status: { in: ["RESERVED", "ONGOING", "OVERDUE"] },
OR: [
{
from: { lte: booking.to },
to: { gte: booking.from },
},
{
from: { gte: booking.from },
to: { lte: booking.to },
},
],
}
: {}),
include: {
user: true,
},
orderBy: {
userId: "asc",
},
}),
/** We create a teamMember entry to represent the org owner.
* Most important thing is passing the ID of the owner as the userId as we are currently only supporting
* assigning custody to users, not NRM.
*/
db.organization.findUnique({
where: {
id: organizationId,
},
select: {
owner: true,
},
}),
/**
* We need to do this in a separate query because we need to filter the bookings within an asset based on the booking.from and booking.to
* That way we know if the asset is available or not because we can see if they are booked for the same period
*/
db.asset.findMany({
where: {
id: {
in: booking?.assets.map((a) => a.id) || [],
},
},
},
});
include: {
category: true,
custody: true,
bookings: {
where: {
// id: { not: booking.id },
...(booking.from && booking.to
? {
status: { in: ["RESERVED", "ONGOING", "OVERDUE"] },
OR: [
{
from: { lte: booking.to },
to: { gte: booking.from },
},
{
from: { gte: booking.from },
to: { lte: booking.to },
},
],
}
: {}),
},
},
},
}),
]);

if (org?.owner) {
teamMembers.push({
id: "owner",
name: "owner",
user: org.owner,
userId: org.owner.id as string,
organizationId,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
});
}

/** We replace the assets ids in the booking object with the assets fetched in the separate request.
* This is useful for more consistent data in the front-end */
Expand Down

0 comments on commit e3e4539

Please sign in to comment.