< Summary

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

#LineLine coverage
 1import { useEffect, useRef, useState } from "react";
 2import { useFetcher } from "react-router";
 3import type { LocationResponse } from "~/api/client";
 4import { FormField, PanelHeader } from "~/components/panel-ui";
 5import { inputStyle } from "~/lib/styles";
 6
 7type ActionData =
 8  | { ok: true; intent: "create-location"; location: LocationResponse }
 9  | { ok: false; error: string };
 10
 011export function LocationFormPanel({ onClose, onCreated }: {
 12  onClose: () => void;
 13  onCreated: (loc: LocationResponse) => void;
 14}) {
 015  const fetcher = useFetcher<ActionData>();
 016  const [validationError, setValidationError] = useState<string | null>(null);
 017  const submitting = fetcher.state !== "idle";
 18
 019  const actionError = fetcher.state === "idle" && fetcher.data && !fetcher.data.ok
 20    ? fetcher.data.error : null;
 021  const error = validationError ?? actionError;
 22
 023  const onCreatedRef = useRef(onCreated);
 024  useEffect(() => { onCreatedRef.current = onCreated; });
 25
 026  const fetchedRef = useRef(false);
 027  useEffect(() => {
 028    if (fetcher.state === "submitting") { fetchedRef.current = true; return; }
 029    if (!fetchedRef.current || fetcher.state !== "idle" || !fetcher.data) return;
 030    fetchedRef.current = false;
 031    const data = fetcher.data;
 032    if (!data.ok) return;
 033    onCreatedRef.current(data.location);
 34  }, [fetcher.state, fetcher.data]);
 35
 036  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
 037    e.preventDefault();
 038    const fd = new FormData(e.currentTarget);
 039    const name = (fd.get("name") as string ?? "").trim();
 040    if (!name) { setValidationError("Name is required."); return; }
 041    setValidationError(null);
 042    fetcher.submit(fd, { method: "post" });
 43  }
 44
 045  return (
 46    <aside className="tui-panel" style={{
 47      width: 340, borderLeft: "1px solid var(--c-border)",
 48      background: "var(--c-bg-2)", flexShrink: 0,
 49      display: "flex", flexDirection: "column", overflowY: "auto",
 50    }}>
 51      <span className="tui-panel-title">─[ new location ]─</span>
 52      <PanelHeader label="NEW LOCATION" onClose={onClose} />
 53      <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: 14, padding: 16 }}>
 54        <input type="hidden" name="_intent" value="create-location" />
 55        {error && <div style={{ fontSize: 12, color: "#ef4444", padding: "6px 10px", background: "rgba(239,68,68,0.08)",
 56        <FormField label="Name *">
 57          <input name="name" type="text" required autoFocus placeholder="e.g. Home, Storage Unit" style={inputStyle} />
 58        </FormField>
 59        <FormField label="Description">
 60          <textarea name="description" rows={2} placeholder="e.g. Main house on Oak Street" style={{ ...inputStyle, resi
 61        </FormField>
 62        <div style={{ display: "flex", gap: 8 }}>
 63          <button type="submit" disabled={submitting} style={{
 64            flex: 1, padding: "8px 14px", borderRadius: 6, border: "none",
 65            background: "var(--c-accent)", color: "#fff", fontSize: 13, fontWeight: 500,
 66            cursor: submitting ? "not-allowed" : "pointer", opacity: submitting ? 0.7 : 1, fontFamily: "inherit",
 67          }}>
 68            {submitting ? "Creating…" : "Create location"}
 69          </button>
 70          <button type="button" onClick={onClose} disabled={submitting} style={{
 71            padding: "8px 14px", borderRadius: 6, border: "1px solid var(--c-border)",
 72            background: "transparent", color: "var(--c-fg-2)", fontSize: 13, cursor: "pointer", fontFamily: "inherit",
 73          }}>Cancel</button>
 74        </div>
 75        <p style={{ fontSize: 11, color: "var(--c-fg-3)", margin: 0 }}>
 76          After creating the location you&apos;ll be able to add a room.
 77        </p>
 78      </form>
 79    </aside>
 80  );
 81}