< Summary

Information
Class: locations.rooms.items.edit.tsx
Assembly: app.routes
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/routes/locations.rooms.items.edit.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 44
Coverable lines: 44
Total lines: 139
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 33
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/routes/locations.rooms.items.edit.tsx

#LineLine coverage
 1import { redirect } from "react-router";
 2import type { Route } from "./+types/locations.rooms.items.edit";
 3import { deleteItem, getLocation, getRoom, getItem, updateItem } from "~/api/client";
 4import { Breadcrumb } from "~/components/breadcrumb";
 5import { ItemForm } from "~/components/items";
 6import { tryApi } from "~/lib/action-helpers.server";
 7import { useToastFromActionData } from "~/lib/toasts";
 8import { pushFlash } from "~/lib/toasts.server";
 9
 010export async function loader({ params, request }: Route.LoaderArgs) {
 011  const locationId = Number(params.id);
 012  const roomId = Number(params.roomId);
 013  const itemId = Number(params.itemId);
 014  if (
 15    Number.isNaN(locationId) ||
 16    Number.isNaN(roomId) ||
 17    Number.isNaN(itemId)
 18  ) {
 019    throw new Response("Not found", { status: 404 });
 20  }
 021  const [location, room, item] = await Promise.all([
 22    getLocation(locationId, request),
 23    getRoom(roomId, request),
 24    getItem(itemId, request),
 25  ]);
 026  if (
 27    !location ||
 28    !room ||
 29    !item ||
 30    room.locationId !== locationId ||
 31    item.roomId !== roomId
 32  ) {
 033    throw new Response("Not found", { status: 404 });
 34  }
 035  return { location, room, item } as const;
 36}
 37
 038export async function action({ request, params }: Route.ActionArgs) {
 039  const itemId = Number(params.itemId);
 040  const roomId = Number(params.roomId);
 041  const locationId = Number(params.id);
 042  if (Number.isNaN(itemId) || Number.isNaN(roomId) || Number.isNaN(locationId)) {
 043    throw new Response("Not found", { status: 404 });
 44  }
 045  const formData = await request.formData();
 046  if (formData.get("_action") === "delete") {
 047    const del = await tryApi(() => deleteItem(itemId, request));
 048    if (!del.ok) return del;
 049    await pushFlash(request, { kind: "success", message: "Item deleted" });
 050    return redirect(`/locations/${locationId}/rooms/${roomId}/items`);
 51  }
 052  const name = formData.get("name");
 053  const description = formData.get("description");
 054  const category = formData.get("category");
 055  const notes = formData.get("notes");
 056  if (typeof name !== "string" || !name.trim()) {
 057    return { ok: false as const, error: "Name is required" };
 58  }
 059  const result = await tryApi(() =>
 60    updateItem(
 61      itemId,
 62      {
 63        roomId,
 64        name: name.trim(),
 65        description:
 66          typeof description === "string" && description.trim()
 67            ? description.trim()
 68            : undefined,
 69        category:
 70          typeof category === "string" && category.trim()
 71            ? category.trim()
 72            : undefined,
 73        notes:
 74          typeof notes === "string" && notes.trim() ? notes.trim() : undefined,
 75      },
 76      request,
 77    ),
 78  );
 079  if (!result.ok) return result;
 080  await pushFlash(request, {
 81    kind: "success",
 82    message: `Item "${result.data.name}" updated`,
 83  });
 084  return redirect(`/locations/${locationId}/rooms/${roomId}/items`);
 85}
 86
 087export function meta({ loaderData }: Route.MetaArgs) {
 088  const name = loaderData?.item?.name ?? "Item";
 089  return [{ title: `Edit ${name} | ClutterStock` }];
 90}
 91
 92type LoaderData = Awaited<ReturnType<typeof loader>>;
 93
 094export default function LocationsRoomsItemsEdit({
 95  loaderData,
 96  actionData,
 97}: Route.ComponentProps) {
 098  useToastFromActionData(actionData);
 099  if (!loaderData) return null;
 0100  const { location, room, item } = loaderData as LoaderData;
 0101  const locationId = location.id!;
 0102  const roomId = room.id!;
 103  const error =
 0104    actionData != null &&
 105    typeof actionData === "object" &&
 106    "error" in actionData
 107      ? (actionData as { error: string }).error
 108      : undefined;
 109  const fieldErrors =
 0110    actionData != null &&
 111    typeof actionData === "object" &&
 112    "fieldErrors" in actionData
 113      ? (actionData as { fieldErrors?: Record<string, string[]> }).fieldErrors
 114      : undefined;
 115
 0116  return (
 117    <>
 118      <Breadcrumb
 119        crumbs={[
 120          { label: "Locations", to: "/locations" },
 121          { label: location.name ?? "Location", to: `/locations/${locationId}/edit` },
 122          { label: "Rooms", to: `/locations/${locationId}/rooms` },
 123          { label: room.name ?? "Room", to: `/locations/${locationId}/rooms/${roomId}/edit` },
 124          { label: "Items", to: `/locations/${locationId}/rooms/${roomId}/items` },
 125          { label: item.name ?? "Item" },
 126        ]}
 127      />
 128      <ItemForm
 129        title="Edit item"
 130        submitLabel="Save"
 131        error={error}
 132        fieldErrors={fieldErrors}
 133        item={item}
 134        roomId={roomId}
 135        cancelTo={`/locations/${locationId}/rooms/${roomId}/items`}
 136      />
 137    </>
 138  );
 139}