< Summary

Information
Class: room-form-panel.tsx
Assembly: app.features.rooms
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/features/rooms/room-form-panel.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 28
Coverable lines: 28
Total lines: 92
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
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/features/rooms/room-form-panel.tsx

#LineLine coverage
 1import { useEffect, useRef, useState } from "react";
 2import { useFetcher } from "react-router";
 3import type { LocationResponse, RoomResponse } from "~/api/client";
 4import { FormField, PanelHeader } from "~/components/panel-ui";
 5import { inputStyle } from "~/lib/styles";
 6
 7type ActionData =
 8  | { ok: true; intent: "create-room"; room: RoomResponse }
 9  | { ok: false; error: string };
 10
 011export function RoomFormPanel({ locations, defaultLocationId, onClose, onCreated }: {
 12  locations: LocationResponse[];
 13  defaultLocationId?: number;
 14  onClose: () => void;
 15  onCreated: (room: RoomResponse) => void;
 16}) {
 017  const fetcher = useFetcher<ActionData>();
 018  const [validationError, setValidationError] = useState<string | null>(null);
 019  const submitting = fetcher.state !== "idle";
 20
 021  const actionError = fetcher.state === "idle" && fetcher.data && !fetcher.data.ok
 22    ? fetcher.data.error : null;
 023  const error = validationError ?? actionError;
 24
 025  const onCreatedRef = useRef(onCreated);
 026  useEffect(() => { onCreatedRef.current = onCreated; });
 27
 028  const fetchedRef = useRef(false);
 029  useEffect(() => {
 030    if (fetcher.state === "submitting") { fetchedRef.current = true; return; }
 031    if (!fetchedRef.current || fetcher.state !== "idle" || !fetcher.data) return;
 032    fetchedRef.current = false;
 033    const data = fetcher.data;
 034    if (!data.ok) return;
 035    onCreatedRef.current(data.room);
 36  }, [fetcher.state, fetcher.data]);
 37
 038  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
 039    e.preventDefault();
 040    const fd = new FormData(e.currentTarget);
 041    const name = (fd.get("name") as string ?? "").trim();
 042    const locationId = Number(fd.get("locationId"));
 043    if (!name) { setValidationError("Name is required."); return; }
 044    if (!locationId) { setValidationError("Select a location."); return; }
 045    setValidationError(null);
 046    fetcher.submit(fd, { method: "post" });
 47  }
 48
 049  return (
 50    <aside className="tui-panel" style={{
 51      width: 340, borderLeft: "1px solid var(--c-border)",
 52      background: "var(--c-bg-2)", flexShrink: 0,
 53      display: "flex", flexDirection: "column", overflowY: "auto",
 54    }}>
 55      <span className="tui-panel-title">─[ new room ]─</span>
 56      <PanelHeader label="NEW ROOM" onClose={onClose} />
 57      <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: 14, padding: 16 }}>
 58        <input type="hidden" name="_intent" value="create-room" />
 59        {error && <div style={{ fontSize: 12, color: "#ef4444", padding: "6px 10px", background: "rgba(239,68,68,0.08)",
 60        <FormField label="Location">
 61          <select name="locationId" defaultValue={defaultLocationId ?? locations[0]?.id ?? ""} style={inputStyle}>
 062            {locations.map(loc => (
 063              <option key={loc.id} value={loc.id}>{loc.name}</option>
 64            ))}
 65          </select>
 66        </FormField>
 67        <FormField label="Name *">
 68          <input name="name" type="text" required autoFocus placeholder="e.g. Garage, Living Room" style={inputStyle} />
 69        </FormField>
 70        <FormField label="Description">
 71          <textarea name="description" rows={2} placeholder="e.g. North-facing bedroom" style={{ ...inputStyle, resize: 
 72        </FormField>
 73        <div style={{ display: "flex", gap: 8 }}>
 74          <button type="submit" disabled={submitting} style={{
 75            flex: 1, padding: "8px 14px", borderRadius: 6, border: "none",
 76            background: "var(--c-accent)", color: "#fff", fontSize: 13, fontWeight: 500,
 77            cursor: submitting ? "not-allowed" : "pointer", opacity: submitting ? 0.7 : 1, fontFamily: "inherit",
 78          }}>
 79            {submitting ? "Creating…" : "Create room"}
 80          </button>
 81          <button type="button" onClick={onClose} disabled={submitting} style={{
 82            padding: "8px 14px", borderRadius: 6, border: "1px solid var(--c-border)",
 83            background: "transparent", color: "var(--c-fg-2)", fontSize: 13, cursor: "pointer", fontFamily: "inherit",
 84          }}>Cancel</button>
 85        </div>
 86        <p style={{ fontSize: 11, color: "var(--c-fg-3)", margin: 0 }}>
 87          After creating the room you&apos;ll be able to add items to it.
 88        </p>
 89      </form>
 90    </aside>
 91  );
 92}