Files
BarangaySystem/.claude/plans/d42061a3fa5b1ccc8fd9cb99ecc7ea58-complete.md
2026-06-06 18:43:00 +08:00

99 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
task: Fix broken layout on /add-products-to-store--h:<storeHash> — products render outside their cards, sometimes don't load, and the card boxes appear huge but empty.
cycles: 5
context: true
private: false
started: 2026-05-17T00:00:00Z
finished: 2026-05-17T00:00:00Z
---
## files
- resources/js/Pages/AddProductsToStore.vue [lines 202-393, 395-459] — page template + scoped CSS for `.product-pick-card`, `.product-thumb`, `.sticky-bottom-bar`. Product grid uses `.row g-2` with `col-12 col-sm-6 col-lg-4`. Step 1 picker is where boxes look huge/empty.
- resources/js/Components/Core/CardSimple.vue [lines 37-47, 79-97] — wrapper used 3× on this page. Has `height: 100%; display: flex; flex-direction: column;` + `.is-premium` (default true) adds white translucent background + hover `transform: translateY(-2px)`. Hover transform on the surrounding card (when product list is *inside* it) makes the whole grid jitter.
- resources/js/Components/Core/FileImage.vue [lines 1-38] — emits `<img>` directly. Page wraps it in `.product-thumb` with `:deep(img) { width: 100%; height: 100%; object-fit: cover }`. If `photourl` is `null`/missing, `p.photourl && p.photourl[0]` passes empty string → FileImage falls back. But if `photourl` is a *string* (not array), `p.photourl[0]` returns the first character — produces a 1-char src that 404s, briefly showing a blank box before `@error` swaps in the fallback. This is the "sometimes does not load" symptom.
- resources/js/Pages/BatchAddProducts.vue — sister page; reference for known-good layout patterns (cross-check spacing, container, image handling).
- ai-docs/dictionary.md — read first per repo CLAUDE.md. Update with any new findings about `FileImage` array-vs-string handling and `CardSimple` hover side-effects.
## steps
1. Read `ai-docs/dictionary.md` first (repo rule). Then open `resources/js/Pages/AddProductsToStore.vue` and reproduce mentally: the picker grid at lines 258286 is **not** wrapped in `CardSimple`, so it sits directly inside `.tf-container`. Confirm by inspecting structure.
2. **Image robustness (root of "sometimes doesn't load" + tiny/broken thumbs):** In `AddProductsToStore.vue` lines 264 and 349, the `:src` binding is `p.photourl && p.photourl[0] ? p.photourl[0] : ''`. Replace both occurrences with a safe accessor:
```js
:src="Array.isArray(p.photourl) ? (p.photourl[0] || '') : (p.photourl || '')"
```
Add a small helper `const firstPhoto = (v) => Array.isArray(v) ? (v[0] || '') : (v || '');` in `<script setup>` and use `firstPhoto(p.photourl)` / `firstPhoto(r.photourl)` in the template.
3. **Thumb sizing (the "huge empty box"):** The `.product-thumb` is 56×56 and `.product-thumb-sm` is 40×40 — these are fine in isolation, but `.product-pick-card` uses `d-flex gap-3 align-items-center` with `.flex-grow-1 min-w-0` *and* a `form-check` on the right. On narrow viewports (`col-12`), if `p.name` is long and `text-truncate` is bypassed by Bootstrap's flex defaults, the row grows tall. Audit:
- Ensure `.product-pick-card` has `min-height` set (e.g. `min-height: 84px;`) so cards have a consistent floor and don't appear "empty huge" while image loads.
- Add `align-items: center; min-width: 0;` to the inner `.d-flex` wrapper (Bootstrap already does this, but verify).
- Constrain the card body so the thumb cannot stretch: ensure `.product-thumb` keeps `flex-shrink: 0` (already set) and confirm it isn't being stretched by `align-items: stretch` from any parent.
4. **Row equal-height (cards "huge but empty" when description is short):** Bootstrap `.row.g-2` makes cols equal-height by default (`align-items: stretch`). Each col contains a single `.product-pick-card` that fills the column. When ONE card in a row has a long name (wraps to 2 lines) the others stretch to match — those look "empty huge". Fix by either:
- Adding `align-self: start` to `.product-pick-card`, OR
- Adding `.row.g-2 { align-items: flex-start; }` scoped to the page (preferred — keeps cards as tall as their own content).
5. **Products outside the box:** Inspect `.tf-container` (global class). If it provides `max-width` + `margin: 0 auto`, the picker grid should sit inside it. Confirm the grid is INSIDE the `<div class="tf-container mt-4">` block — currently it is. But the `.sticky-bottom-bar` at line 288 uses `position: sticky; bottom: 12px;` with its own background — on small screens the sticky bar can overlap product cards if the parent has `overflow: hidden`. Check `CardSimple` — `.card-custom` has `overflow: hidden` (line 42). The sticky bar is OUTSIDE CardSimple so that's fine, but verify by browsing the rendered page that no card has `overflow: hidden` cropping the checkbox/title.
6. **CardSimple hover transform causing visual "popping out":** `.is-premium:hover { transform: translateY(-2px) }` (CardSimple line 94-97) applies to the *page-level* CardSimples (target store header, search bar). When the user mouses over them they lift, which can look like products jumping out of the box. Either pass `:is-premium="false"` on the search-bar/header CardSimples on this page (they shouldn't lift), or scope the hover lift to a class that's only added when used as a clickable card.
7. **Empty state height:** Lines 254-257 — `text-center py-5 text-muted` for the empty state. Ensure `py-5` is enough that the empty state doesn't collapse into a thin strip after the search input. Likely fine; verify visually.
8. **Loading state:** Line 251-253 — `text-center py-5` for the spinner. Same — fine.
9. **Theming compliance (per CLAUDE.md):** No `bg-white`, `bg-light`, or `text-dark` in this file — confirmed clean. Do not introduce them.
10. **Sticky-top header offset:** Page does not use `sticky-top` on a child header — N/A.
11. **VueRouteMap:** Verify `AddProductsToStore` is registered in `app/Http/Controllers/Support/VueRouteMap.php` with appropriate `allowedUserTypes`. If missing (the URL `/add-products-to-store--h:…` is reachable, so likely present), no change. Otherwise add it in the same commit.
12. After edits, run `npm run build` (or `npm run dev` if Vite is hot-reloading) and visually verify in browser at `/add-products-to-store--h:<a real store hash>`. Confirm: (a) cards are uniform, hugging their content; (b) thumbnails always render (fallback kicks in when photourl is empty/string); (c) nothing overflows the page container; (d) sticky bottom bar stays inside the container.
13. Update `ai-docs/dictionary.md` with: (a) note that `FileImage` expects `string|array|null` for `src` and pages must pass via a safe accessor — never `arr[0]` directly on an unknown shape; (b) note that `CardSimple` is `is-premium` by default and lifts on hover — pass `:is-premium="false"` for non-interactive panels.
## context
### AddProductsToStore.vue — product card (Step 1 picker), lines 258-286
```html
<div v-else class="row g-2">
<div v-for="p in filteredProducts" :key="p.hashkey" class="col-12 col-sm-6 col-lg-4">
<div class="product-pick-card" :class="{ picked: selected[p.hashkey] }"
@click="toggleProduct(p.hashkey)">
<div class="d-flex gap-3 align-items-center">
<div class="product-thumb">
<FileImage :src="p.photourl && p.photourl[0] ? p.photourl[0] : ''"
class="img-fluid rounded" alt="Product"
fallback="https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/146710fe9ece.bin" />
</div>
<div class="flex-grow-1 min-w-0"> … name / category / price … </div>
<div class="form-check"><input type="checkbox" … /></div>
</div>
</div>
</div>
</div>
```
### AddProductsToStore.vue — scoped CSS, lines 395-459 (key blocks)
```css
.product-pick-card { border: 1px solid …; border-radius: 12px; padding: 12px; cursor: pointer; }
.product-thumb { width: 56px; height: 56px; flex-shrink: 0; overflow: hidden; border-radius: 8px; }
.product-thumb :deep(img) { width: 100%; height: 100%; object-fit: cover; }
.sticky-bottom-bar { position: sticky; bottom: 12px; … border-radius: 14px; z-index: 5; }
```
### FileImage.vue — relevant logic, lines 14-33
```js
const processedSrc = computed(() => {
if (!props.src) return props.fallback;
if (Array.isArray(props.src)) return props.src[0]; // safe array handling here
if (props.src.startsWith("http") || props.src.startsWith("/") || props.src.startsWith("data:")) return props.src;
if (props.src.length > 20 && !props.src.includes(".") && !props.src.includes("/")) return `/RequestData/File/${props.src}`;
return props.src;
});
```
Key insight: `FileImage` itself already handles arrays. The bug is the *caller* unsafely doing `p.photourl[0]` — if `photourl` is a string, this yields one character.
### CardSimple.vue — hover transform, lines 80-97
```css
.is-premium { background: rgba(255,255,255,0.6); backdrop-filter: blur(12px); border: 1px solid rgba(0,0,0,0.05); box-shadow: 0 8px 32px 0 rgba(31,38,135,0.07); }
.is-premium:hover { border-color: var(--accent-color, rgba(83,61,234,0.3)); transform: translateY(-2px); }
```
`is-premium` defaults to `true`. Non-interactive panels (target store header, search bar) shouldn't lift.
## notes
- dictionary: ai-docs/dictionary.md — MUST read first and update after (repo CLAUDE.md mandate).
- linters: eslint (via npm), no phpcs, tsc, pylint relevant for this Vue file. Run `npm run build` or `npm run lint` if available.
- constraints:
- Repo CLAUDE.md: no `bg-white`/`bg-light`/`text-dark` hardcoded. Theme via CSS vars. This file is currently compliant — keep it so.
- Vue page edits don't require touching `routes/web.php`, `UserActions`, `UserPermissions`, or `VueRouteMap` unless registration is missing.
- Do NOT modify legacy Blade. All work is in `.vue` files + scoped CSS.
- Test direct URL access (reload at `/add-products-to-store--h:<hash>`) after fix.
- Keep edits minimal — fix the layout regressions, do not refactor the component.