< Summary

Information
Class: items-list.tsx
Assembly: app.components.items
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/components/items/items-list.tsx
Tag: 58_25416222083
Line coverage
0%
Covered lines: 0
Uncovered lines: 19
Coverable lines: 19
Total lines: 128
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 6
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/components/items/items-list.tsx

#LineLine coverage
 1import { Form, Link } from "react-router";
 2import type { ItemResponse } from "~/api/client";
 3
 4type ItemsListProps = {
 5  locationId: number;
 6  roomId: number;
 7  roomName: string;
 8  items: ItemResponse[];
 9};
 10
 011const CATEGORY_COLORS: Record<string, { bg: string; fg: string }> = {
 12  Furniture:    { bg: "rgba(91,91,245,0.10)",   fg: "#5b5bf5" },
 13  Electronics:  { bg: "rgba(34,197,94,0.10)",   fg: "#16a34a" },
 14  Textiles:     { bg: "rgba(244,114,182,0.10)",  fg: "#db2777" },
 15  Cookware:     { bg: "rgba(249,115,22,0.10)",   fg: "#ea580c" },
 16  Appliances:   { bg: "rgba(239,68,68,0.10)",    fg: "#dc2626" },
 17  Lighting:     { bg: "rgba(234,179,8,0.10)",    fg: "#ca8a04" },
 18  Decor:        { bg: "rgba(168,85,247,0.10)",   fg: "#9333ea" },
 19  Plants:       { bg: "rgba(34,197,94,0.10)",    fg: "#16a34a" },
 20  Tableware:    { bg: "rgba(20,184,166,0.10)",   fg: "#0d9488" },
 21  Sports:       { bg: "rgba(14,165,233,0.10)",   fg: "#0284c7" },
 22  Media:        { bg: "rgba(168,85,247,0.10)",   fg: "#9333ea" },
 23  Seasonal:     { bg: "rgba(234,179,8,0.10)",    fg: "#ca8a04" },
 24  Travel:       { bg: "rgba(14,165,233,0.10)",   fg: "#0284c7" },
 25};
 26
 027function nameHue(name: string): number {
 028  let h = 0;
 029  for (let i = 0; i < name.length; i++) h = ((h * 31) + name.charCodeAt(i)) >>> 0;
 030  return h % 360;
 31}
 32
 033function ItemThumb({ name }: { name: string }) {
 034  const hue = nameHue(name);
 035  return (
 36    <div style={{
 37      width: 28,
 38      height: 28,
 39      borderRadius: 6,
 40      background: `linear-gradient(135deg, oklch(0.85 0.05 ${hue}), oklch(0.70 0.07 ${(hue + 40) % 360}))`,
 41      border: "1px solid var(--c-border)",
 42      flexShrink: 0,
 43    }} />
 44  );
 45}
 46
 047function CategoryTag({ name }: { name: string }) {
 048  const c = CATEGORY_COLORS[name] ?? { bg: "rgba(120,120,140,0.10)", fg: "var(--c-fg-2)" };
 049  return (
 50    <span style={{
 51      fontSize: 11,
 52      padding: "2px 8px",
 53      borderRadius: 4,
 54      background: c.bg,
 55      color: c.fg,
 56      fontWeight: 500,
 57      whiteSpace: "nowrap",
 58    }}>
 59      {name}
 60    </span>
 61  );
 62}
 63
 064export function ItemsList({ locationId, roomId, roomName, items }: ItemsListProps) {
 065  const base = `/locations/${locationId}/rooms/${roomId}/items`;
 66
 067  if (items.length === 0) {
 068    return (
 69      <div className="card-empty">
 70        <p className="text-muted">No items in {roomName} yet.</p>
 71        <Link to={`${base}/new`} className="link-text" style={{ display: "inline-block", marginTop: 12 }}>
 72          Add the first item →
 73        </Link>
 74      </div>
 75    );
 76  }
 77
 078  return (
 79    <table className="console-table">
 80      <thead>
 81        <tr>
 82          <th>Item</th>
 83          <th>Category</th>
 84          <th>Notes</th>
 85          <th style={{ width: 1, whiteSpace: "nowrap" }}></th>
 86        </tr>
 87      </thead>
 88      <tbody>
 089        {items.map((item) => (
 090          <tr key={item.id}>
 91            <td>
 92              <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
 93                <ItemThumb name={item.name ?? ""} />
 94                <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
 95                  <span style={{ fontWeight: 500 }}>{item.name ?? "Unnamed"}</span>
 96                  {item.description && (
 97                    <span style={{ fontSize: 11, color: "var(--c-fg-3)" }}>{item.description}</span>
 98                  )}
 99                </div>
 100              </div>
 101            </td>
 102            <td>
 103              {item.category ? <CategoryTag name={item.category} /> : <span style={{ color: "var(--c-fg-3)" }}>—</span>}
 104            </td>
 105            <td style={{ color: "var(--c-fg-3)", fontSize: 12 }}>
 106              {item.notes || "—"}
 107            </td>
 108            <td>
 109              <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
 110                <Link to={`${base}/${item.id}/edit`} className="link-chip">Edit</Link>
 111                <Form
 112                  method="post"
 0113                  onSubmit={(e) => { if (!confirm("Delete this item?")) e.preventDefault(); }}
 114                >
 115                  <input type="hidden" name="_action" value="delete" />
 116                  <input type="hidden" name="id" value={item.id} />
 117                  <button type="submit" className="link-chip" style={{ color: "var(--c-danger)", borderColor: "transpare
 118                    Delete
 119                  </button>
 120                </Form>
 121              </div>
 122            </td>
 123          </tr>
 124        ))}
 125      </tbody>
 126    </table>
 127  );
 128}