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

9.8 KiB
Raw Permalink Blame History

task, cycles, context, private, started, finished
task cycles context private started finished
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. 5 true false 2026-05-17T00:00:00Z 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:
    :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

<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)

.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

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

.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.