Skip to content

Commit

Permalink
Merge branch 'main' into feature/select-all-on-manage-assets
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko authored Dec 12, 2024
2 parents 513e048 + f4c3e74 commit abac92d
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 100 deletions.
12 changes: 11 additions & 1 deletion app/components/layout/sidebar/sidebar-user-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { NavLink, useFetcher, useLoaderData } from "@remix-run/react";
import { LogOutIcon, UserRoundIcon } from "lucide-react";
import { LogOutIcon, UserPenIcon, UserRoundIcon } from "lucide-react";
import { ChevronRight } from "~/components/icons/library";
import {
DropdownMenu,
Expand Down Expand Up @@ -77,6 +77,16 @@ export default function SidebarUserMenu() {
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
asChild
className="cursor-pointer gap-2 border-b border-gray-200 p-2"
onClick={closeDropdown}
>
<NavLink to="/me">
<UserPenIcon className="size-4" />
My Profile
</NavLink>
</DropdownMenuItem>
<DropdownMenuItem
asChild
className="cursor-pointer gap-2 border-b border-gray-200 p-2"
Expand Down
86 changes: 84 additions & 2 deletions app/modules/asset/service.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
ErrorCorrection,
Prisma,
} from "@prisma/client";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { redirect, type LoaderFunctionArgs } from "@remix-run/node";
import type {
SortingDirection,
SortingOptions,
Expand All @@ -42,7 +42,12 @@ import {
} from "~/modules/team-member/service.server";
import type { AllowedModelNames } from "~/routes/api+/model-filters";

import { updateCookieWithPerPage } from "~/utils/cookies.server";
import {
getFiltersFromRequest,
setCookie,
updateCookieWithPerPage,
userPrefs,
} from "~/utils/cookies.server";
import {
buildCustomFieldValue,
extractCustomFieldValuesFromPayload,
Expand Down Expand Up @@ -3022,3 +3027,80 @@ export async function relinkQrCode({
}),
]);
}

export async function getAssetsTabLoaderData({
userId,
request,
organizationId,
}: {
userId: User["id"];
request: Request;
organizationId: Organization["id"];
}) {
try {
const { filters, redirectNeeded } = await getFiltersFromRequest(
request,
organizationId
);

if (filters && redirectNeeded) {
const cookieParams = new URLSearchParams(filters);
return redirect(`/assets?${cookieParams.toString()}`);
}

const filtersSearchParams = new URLSearchParams(filters);
filtersSearchParams.set("teamMember", userId);

const {
search,
totalAssets,
perPage,
page,
categories,
tags,
assets,
totalPages,
cookie,
totalCategories,
totalTags,
locations,
totalLocations,
} = await getPaginatedAndFilterableAssets({
request,
organizationId,
filters: filtersSearchParams.toString(),
});

const modelName = {
singular: "asset",
plural: "assets",
};

const userPrefsCookie = await userPrefs.serialize(cookie);
const headers = [setCookie(userPrefsCookie)];

return {
search,
totalItems: totalAssets,
perPage,
page,
categories,
tags,
items: assets,
totalPages,
cookie,
totalCategories,
totalTags,
locations,
totalLocations,
modelName,
headers,
};
} catch (cause) {
throw new ShelfError({
cause,
label,
message: "Something went wrong while fetching assets",
});
}
}
26 changes: 26 additions & 0 deletions app/modules/booking/service.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { db } from "~/database/db.server";
import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template";
import { sendEmail } from "~/emails/mail.server";
import { getStatusClasses, isOneDayEvent } from "~/utils/calendar";
import { getDateTimeFormat } from "~/utils/client-hints";
import { calcTimeDifference } from "~/utils/date-fns";
import { sendNotification } from "~/utils/emitter/send-notification.server";
import type { ErrorLabel } from "~/utils/error";
Expand Down Expand Up @@ -1654,6 +1655,31 @@ export async function getExistingBookingDetails(bookingId: string) {
}
}

export function formatBookingsDates(bookings: Booking[], request: Request) {
return bookings.map((b) => {
if (b.from && b.to) {
const from = new Date(b.from);
const displayFrom = getDateTimeFormat(request, {
dateStyle: "short",
timeStyle: "short",
}).format(from);

const to = new Date(b.to);
const displayTo = getDateTimeFormat(request, {
dateStyle: "short",
timeStyle: "short",
}).format(to);

return {
...b,
displayFrom: displayFrom.split(","),
displayTo: displayTo.split(","),
};
}
return b;
});
}

export async function getAvailableAssetsIdsForBooking(
assetIds: Asset["id"][]
): Promise<string[]> {
Expand Down
2 changes: 1 addition & 1 deletion app/routes/$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const meta = () => [{ title: appendToMetaTitle("Not found") }];

export default function LayoutSplat() {
return (
<div className="flex size-full items-center justify-center">
<div className="flex size-full h-screen items-center justify-center">
<div className="flex flex-col items-center text-center">
<span className="mb-5 size-[56px] text-primary">
<ErrorIcon />
Expand Down
2 changes: 2 additions & 0 deletions app/routes/_layout+/bookings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export default function BookingsIndexPage({
"$assetId.bookings",
"$userId.bookings",
"bookings.update-existing",
"me.bookings",
];

const shouldRenderIndex = allowedRoutes.includes(currentRoute?.handle?.name);
Expand All @@ -237,6 +238,7 @@ export default function BookingsIndexPage({
const isChildBookingsPage = [
"$assetId.bookings",
"$userId.bookings",
"me.bookings",
].includes(currentRoute?.handle?.name);

const isBookingUpdateExisting =
Expand Down
11 changes: 11 additions & 0 deletions app/routes/_layout+/me._index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { redirect } from "@remix-run/node";

/**
* We do not render anything on /me
* We just redirect to default sub-route which is /assets
*/
export function loader() {
return redirect("assets");
}

export const shouldRevalidate = () => false;
48 changes: 48 additions & 0 deletions app/routes/_layout+/me.assets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { getAssetsTabLoaderData } from "~/modules/asset/service.server";
import { makeShelfError } from "~/utils/error";
import { data, error } from "~/utils/http.server";
import {
PermissionAction,
PermissionEntity,
} from "~/utils/permissions/permission.data";
import { requirePermission } from "~/utils/roles.server";
import { AssetsList } from "./assets._index";

export async function loader({ request, context }: LoaderFunctionArgs) {
const authSession = context.getSession();
const userId = authSession.userId;

try {
const { organizationId } = await requirePermission({
userId,
request,
entity: PermissionEntity.asset,
action: PermissionAction.read,
});

const { headers, ...loaderData } = await getAssetsTabLoaderData({
userId,
request,
organizationId,
});

return json(data(loaderData), { headers });
} catch (cause) {
const reason = makeShelfError(cause, { userId });
throw json(error(reason), { status: reason.status });
}
}

export default function MyAssets() {
return (
<AssetsList
disableTeamMemberFilter
disableBulkActions
customEmptyState={{
title: "No assets",
text: "You have not created any assets yet and no assets are assigned to you.",
}}
/>
);
}
86 changes: 86 additions & 0 deletions app/routes/_layout+/me.bookings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import type { HeaderData } from "~/components/layout/header/types";
import {
formatBookingsDates,
getBookings,
} from "~/modules/booking/service.server";
import { updateCookieWithPerPage } from "~/utils/cookies.server";
import { makeShelfError } from "~/utils/error";
import { data, error, getCurrentSearchParams } from "~/utils/http.server";
import { getParamsValues } from "~/utils/list";
import {
PermissionAction,
PermissionEntity,
} from "~/utils/permissions/permission.data";
import { requirePermission } from "~/utils/roles.server";
import BookingsIndexPage from "./bookings";

export async function loader({ context, request }: LoaderFunctionArgs) {
const authSession = context.getSession();
const userId = authSession.userId;

try {
const { organizationId } = await requirePermission({
userId,
request,
entity: PermissionEntity.booking,
action: PermissionAction.read,
});

const searchParams = getCurrentSearchParams(request);
const { page, perPageParam, search, status } =
getParamsValues(searchParams);

const cookie = await updateCookieWithPerPage(request, perPageParam);
const { perPage } = cookie;

const { bookings, bookingCount } = await getBookings({
organizationId,
page,
perPage,
search,
userId,
custodianUserId: userId,
...(status && {
// If status is in the params, we filter based on it
statuses: [status],
}),
});

const totalPages = Math.ceil(bookingCount / perPage);

const header: HeaderData = { title: "Bookings" };

const modelName = {
singular: "booking",
plural: "bookings",
};

/** We format the dates on the server based on the users timezone and locale */
const items = formatBookingsDates(bookings, request);

return json(
data({
header,
items,
search,
page,
totalItems: bookingCount,
totalPages,
perPage,
modelName,
})
);
} catch (cause) {
const reason = makeShelfError(cause, { userId });
throw json(error(reason), { status: reason.status });
}
}

export default function MyBookings() {
return <BookingsIndexPage disableBulkActions className="!mt-0" />;
}

export const handle = {
name: "me.bookings",
};
Loading

0 comments on commit abac92d

Please sign in to comment.