diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index b75417a..062de99 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -55,8 +55,8 @@ Requirements for this milestone. Each maps to roadmap phases. - [x] **CATFLOW-04**: Collection items referencing global items display merged data (global base + personal overlay) - [x] **CATFLOW-05**: Thread candidates can be added from catalog with global item link - [x] **CATFLOW-06**: Thread resolution with catalog-linked candidate creates reference item with auto-link -- [ ] **CATFLOW-07**: Manual entry fallback when item not in catalog -- [ ] **CATFLOW-08**: Non-functional "Submit to catalog?" prompt shown after manual save +- [x] **CATFLOW-07**: Manual entry fallback when item not in catalog +- [x] **CATFLOW-08**: Non-functional "Submit to catalog?" prompt shown after manual save ### Item & Catalog Detail Pages @@ -177,8 +177,8 @@ Which phases cover which requirements. Updated during roadmap creation. | CATFLOW-04 | Phase 19 | Complete | | CATFLOW-05 | Phase 19, 22 | Complete | | CATFLOW-06 | Phase 19, 22 | Complete | -| CATFLOW-07 | Phase 23 | Pending | -| CATFLOW-08 | Phase 23 | Pending | +| CATFLOW-07 | Phase 23 | Complete | +| CATFLOW-08 | Phase 23 | Complete | | TAG-01 | Phase 19 | Complete | | TAG-02 | Phase 19 | Complete | | DETAIL-01 | Phase 21 | Pending | diff --git a/.planning/config.json b/.planning/config.json index 07e2f0b..90ead40 100644 --- a/.planning/config.json +++ b/.planning/config.json @@ -3,7 +3,7 @@ "granularity": "coarse", "parallelization": true, "commit_docs": true, - "model_profile": "quality", + "model_profile": "balanced", "workflow": { "research": true, "plan_check": true, diff --git a/CLAUDE.md b/CLAUDE.md index 33eb127..f9c12ad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,12 +86,24 @@ curl -s -X POST "https://gitea.jeanlucmakiola.de/api/v1/repos/makiolaj/GearBox/a `@/*` maps to `./src/*` (configured in tsconfig.json). +## Reusable Components + +Always use existing components instead of rebuilding with plain HTML. Check `src/client/components/` before creating new form elements or UI patterns. + +| Need | Use | Not | +|------|-----|-----| +| Category selection | `CategoryPicker` (icons, search, inline create) | Plain ` setCategoryId(Number(e.target.value))} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent bg-white" - > - {categories?.map((cat) => ( - - ))} - + setCategoryId(id)} + />
diff --git a/src/client/components/CollectionView.tsx b/src/client/components/CollectionView.tsx index 126676c..8394079 100644 --- a/src/client/components/CollectionView.tsx +++ b/src/client/components/CollectionView.tsx @@ -230,6 +230,7 @@ export function CollectionView() { imageFilename={item.imageFilename} imageUrl={item.imageUrl} productUrl={item.productUrl} + brand={item.brand} /> ))}
@@ -264,6 +265,7 @@ export function CollectionView() { categoryIcon={categoryIcon} imageFilename={item.imageFilename} productUrl={item.productUrl} + brand={item.brand} /> ))} diff --git a/src/client/hooks/useItems.ts b/src/client/hooks/useItems.ts index 51c5bf4..6b43281 100644 --- a/src/client/hooks/useItems.ts +++ b/src/client/hooks/useItems.ts @@ -34,6 +34,8 @@ interface ItemWithCategory { notes: string | null; productUrl: string | null; imageFilename: string | null; + globalItemId: number | null; + brand: string | null; createdAt: string; updatedAt: string; categoryName: string; diff --git a/src/client/lib/iconData.tsx b/src/client/lib/iconData.tsx index f106cda..82a0b0c 100644 --- a/src/client/lib/iconData.tsx +++ b/src/client/lib/iconData.tsx @@ -223,6 +223,7 @@ export const iconGroups: IconGroup[] = [ // --- LucideIcon render component --- function toPascalCase(str: string): string { + if (!str) return "Package"; return str .split("-") .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) diff --git a/src/server/services/item.service.ts b/src/server/services/item.service.ts index c0919d2..1e20a2d 100644 --- a/src/server/services/item.service.ts +++ b/src/server/services/item.service.ts @@ -35,6 +35,9 @@ export async function getAllItems(db: Db, userId: number) { )`.as("image_filename"), imageSourceUrl: items.imageSourceUrl, globalItemId: items.globalItemId, + brand: sql< + string | null + >`COALESCE(${globalItems.brand}, ${items.brand})`.as("brand"), createdAt: items.createdAt, updatedAt: items.updatedAt, categoryName: categories.name, @@ -66,6 +69,7 @@ export async function getItemById(db: Db, userId: number, id: number) { ${items.priceCents} )`.as("price_cents"), purchasePriceCents: items.purchasePriceCents, + quantity: items.quantity, categoryId: items.categoryId, notes: items.notes, productUrl: items.productUrl, @@ -75,10 +79,16 @@ export async function getItemById(db: Db, userId: number, id: number) { )`.as("image_filename"), imageSourceUrl: items.imageSourceUrl, globalItemId: items.globalItemId, + brand: sql< + string | null + >`COALESCE(${globalItems.brand}, ${items.brand})`.as("brand"), createdAt: items.createdAt, updatedAt: items.updatedAt, + categoryName: categories.name, + categoryIcon: categories.icon, }) .from(items) + .innerJoin(categories, eq(items.categoryId, categories.id)) .leftJoin(globalItems, eq(items.globalItemId, globalItems.id)) .where(and(eq(items.id, id), eq(items.userId, userId))); @@ -143,6 +153,7 @@ export async function updateItem( imageSourceUrl: string; globalItemId: number; purchasePriceCents: number; + brand: string; }>, ) { // Check if item exists and belongs to user diff --git a/src/shared/schemas.ts b/src/shared/schemas.ts index 0a02453..720cc7a 100644 --- a/src/shared/schemas.ts +++ b/src/shared/schemas.ts @@ -12,6 +12,7 @@ export const createItemSchema = z.object({ quantity: z.number().int().positive().optional(), globalItemId: z.number().int().positive().optional(), purchasePriceCents: z.number().int().nonnegative().optional(), + brand: z.string().optional(), }); export const updateItemSchema = createItemSchema.partial().extend({