99 lines
9.8 KiB
Markdown
99 lines
9.8 KiB
Markdown
---
|
||
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 258–286 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.
|