< Summary

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

#LineLine coverage
 1import { useState } from "react";
 2import type { ItemResponse, LocationResponse, RoomResponse } from "~/api/client";
 3
 04export function Win98Tree({
 5  locations, rooms, items, categories,
 6  filterRoomId, filterCategory,
 7  onSelectRoom, onSelectCategory, onAddRoom,
 8}: {
 9  locations: LocationResponse[];
 10  rooms: RoomResponse[];
 11  items: ItemResponse[];
 12  categories: string[];
 13  filterRoomId: number | null;
 14  filterCategory: string | null;
 15  onSelectRoom: (id: number) => void;
 16  onSelectCategory: (name: string) => void;
 17  onAddRoom: (locationId: number) => void;
 18}) {
 019  const [collapsed, setCollapsed] = useState<Set<number>>(new Set());
 020  const [rootCollapsed, setRootCollapsed] = useState(false);
 021  const [categoriesCollapsed, setCategoriesCollapsed] = useState(false);
 22
 023  const toggle = (id: number) => {
 024    setCollapsed(prev => {
 025      const next = new Set(prev);
 026      if (next.has(id)) next.delete(id);
 027      else next.add(id);
 028      return next;
 29    });
 30  };
 31
 032  return (
 33    <div className="win98-tree">
 34      <Row
 35        chevron={rootCollapsed ? "▶" : "▼"}
 36        icon={<img src="/brand/icon.svg" alt="" width={16} height={16} style={{ display: "block" }} />}
 37        label={<>clutter<span style={{ color: "#c4502a" }}>:stock</span></>}
 38        bold
 039        onChevronClick={() => setRootCollapsed(v => !v)}
 40      />
 41
 42      {!rootCollapsed && (
 43        <div className="win98-tree-indent">
 044          {locations.map(loc => {
 045            const locRooms = rooms.filter(r => r.locationId === loc.id);
 046            const isCollapsed = loc.id != null && collapsed.has(loc.id);
 047            return (
 48              <div key={loc.id}>
 49                <Row
 50                  chevron={locRooms.length > 0 ? (isCollapsed ? "▶" : "▼") : ""}
 51                  icon="🏠"
 52                  label={loc.name ?? "Location"}
 053                  onChevronClick={() => loc.id != null && toggle(loc.id)}
 054                  onIconClick={() => loc.id != null && toggle(loc.id)}
 55                  trailing={
 56                    <button
 57                      type="button"
 058                      onClick={(e) => { e.stopPropagation(); if (loc.id != null) onAddRoom(loc.id); }}
 59                      title="New room"
 60                      className="win98-tree-add"
 61                    >+</button>
 62                  }
 63                />
 64                {!isCollapsed && (
 65                  <div className="win98-tree-indent">
 066                    {locRooms.map(room => {
 067                      const count = items.filter(i => i.roomId === room.id).length;
 068                      const selected = filterRoomId === room.id;
 069                      return (
 70                        <Row
 71                          key={room.id}
 72                          icon="📁"
 73                          label={`${room.name ?? "Room"} (${count})`}
 74                          selected={selected}
 075                          onRowClick={() => room.id != null && onSelectRoom(room.id)}
 76                        />
 77                      );
 78                    })}
 79                    {locRooms.length === 0 && (
 80                      <div className="win98-tree-empty">No rooms yet</div>
 81                    )}
 82                  </div>
 83                )}
 84              </div>
 85            );
 86          })}
 87        </div>
 88      )}
 89
 90      {categories.length > 0 && (
 91        <>
 92          <div className="win98-tree-divider">
 93            <button
 94              type="button"
 095              onClick={() => setCategoriesCollapsed(v => !v)}
 96              className="win98-tree-divider-btn"
 97            >
 98              {categoriesCollapsed ? "▶" : "▼"} Categories
 99            </button>
 100          </div>
 101          {!categoriesCollapsed && (
 102            <div className="win98-tree-indent">
 0103              {categories.map(cat => {
 0104                const count = items.filter(i => i.category === cat).length;
 0105                const selected = filterCategory === cat;
 0106                return (
 107                  <Row
 108                    key={cat}
 109                    icon="🏷"
 110                    label={`${cat} (${count})`}
 111                    selected={selected}
 0112                    onRowClick={() => onSelectCategory(cat)}
 113                  />
 114                );
 115              })}
 116            </div>
 117          )}
 118        </>
 119      )}
 120
 121    </div>
 122  );
 123}
 124
 0125function Row({
 126  chevron, icon, label, selected, bold,
 127  onRowClick, onChevronClick, onIconClick, trailing,
 128}: {
 129  chevron?: string;
 130  icon: React.ReactNode;
 131  label: React.ReactNode;
 132  selected?: boolean;
 133  bold?: boolean;
 134  onRowClick?: () => void;
 135  onChevronClick?: () => void;
 136  onIconClick?: () => void;
 137  trailing?: React.ReactNode;
 138}) {
 0139  const interactive = !!onRowClick;
 0140  return (
 141    <div
 142      className={`win98-tree-row${selected ? " win98-tree-row--selected" : ""}`}
 143      onClick={interactive ? onRowClick : undefined}
 144      role={interactive ? "button" : undefined}
 145      tabIndex={interactive ? 0 : undefined}
 146      style={{ cursor: interactive ? "pointer" : "default" }}
 147    >
 148      <span
 149        className="win98-tree-chevron"
 0150        onClick={onChevronClick ? (e) => { e.stopPropagation(); onChevronClick(); } : undefined}
 151        style={{ cursor: onChevronClick ? "pointer" : "default" }}
 152      >
 153        {chevron ?? ""}
 154      </span>
 155      <span
 156        className="win98-tree-icon"
 0157        onClick={onIconClick ? (e) => { e.stopPropagation(); onIconClick(); } : undefined}
 158      >
 159        {icon}
 160      </span>
 161      <span className="win98-tree-label" style={bold ? { fontWeight: 700 } : undefined}>
 162        {label}
 163      </span>
 164      {trailing && <span className="win98-tree-trailing">{trailing}</span>}
 165    </div>
 166  );
 167}