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

209 lines
7.2 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.

# Plan: Batch Add Products — Photo Upload + Category Dropdown Fix
## Goal
Two fixes for `resources/js/Pages/BatchAddProducts.vue`:
1. **Optional photo upload per product leaf** (new products only) — tap an area to pick a photo, upload immediately via `useFileUpload`, and include the returned hashkey in the submit payload.
2. **Category field converts to a `<select>` dropdown** — currently uses `<input type="text" list="leaf-categories">` which behaves as plain text on mobile. Replace with a `<select>` populated from the `categories` ref (same data already loaded from `/Products/New/Category/Datalist`). Keep "None / other" as the first empty option so the field stays optional.
---
## Context
### Key files
- **Frontend**: `resources/js/Pages/BatchAddProducts.vue` — all changes happen here
- **Composable**: `resources/js/composables/useFileUpload.js``uploadFile(file)` → POST `/File/Upload/ProductMarket` → returns `{ hashkey }`. Already used in `CreateProductStoreOwner.vue`.
- **Backend**: `app/Http/Controllers/Market/BatchController.php``batchCreateProducts()` — needs to accept `photourl` and pass it to `Product::create()`.
- **Product model**: `app/Models/Market/Product.php``photourl` is already a cast array field (line 27/59).
### Category data
`fetchCategories()` already calls `POST /Products/New/Category/Datalist``{ success: true, categories: ['Vegetables', 'Fruits', ...] }`. The raw `categories` ref holds a string array. The `<datalist>` approach already uses it; replacing with `<select>` just maps the same array to `<option>` elements.
### Photo upload flow (existing pattern from CreateProductStoreOwner.vue)
```js
import { useFileUpload } from '../composables/useFileUpload.js'
const { uploadFile, uploadError } = useFileUpload({ category: 'ProductMarket' })
// on file input change:
const result = await uploadFile(file)
if (result?.hashkey) leaf.photoHash = result.hashkey
```
### Leaf state shape (current makeLeaf)
```js
const makeLeaf = () => ({
source: 'new',
product_hash: '',
linked: null,
name: '',
price: 0,
available: 0,
unitname: 'pcs',
description: '',
category: '',
subcategory: '',
barcode: '',
})
```
Add `photoHash: ''` and `photoUploading: false` to this shape.
### Backend payload change
In `saveProducts()`, for `source === 'new'` leaves add:
```js
photourl: p.photoHash ? [p.photoHash] : [],
```
In `BatchController::batchCreateProducts()`, add to the validator for `source === 'new'`:
```php
'photourl' => 'nullable|array',
'photourl.*' => 'nullable|string',
```
And in `Product::create([...])` add:
```php
'photourl' => $productData['photourl'] ?? [],
```
---
## Step-by-step implementation
### Step 1 — Add `photoHash` + `photoUploading` to leaf state
In `makeLeaf()`:
```js
const makeLeaf = () => ({
// ...existing fields...
photoHash: '',
photoUploading: false,
})
```
### Step 2 — Import `useFileUpload` and wire a single uploader instance
Because each leaf uploads independently, instantiate `useFileUpload` once and use a helper:
```js
import { useFileUpload } from '../composables/useFileUpload.js'
const { uploadFile } = useFileUpload({ category: 'ProductMarket' })
const handleLeafPhoto = async (index, event) => {
const file = event.target.files?.[0]
if (!file) return
products.value[index].photoUploading = true
const result = await uploadFile(file)
products.value[index].photoUploading = false
if (result?.hashkey) products.value[index].photoHash = result.hashkey
}
const removeLeafPhoto = (index) => {
products.value[index].photoHash = ''
}
```
### Step 3 — Add photo upload UI inside the `source === 'new'` template block
Place above the description field, after the Barcode row:
```html
<!-- Photo (optional) -->
<label class="form-label small fw-bold text-muted mb-1 mt-1">Photo</label>
<div class="d-flex align-items-center gap-2">
<label
v-if="!product.photoHash"
class="btn btn-outline-secondary btn-sm rounded-pill flex-grow-1"
:class="{ disabled: product.photoUploading }"
:for="`photo-input-${index}`"
style="cursor:pointer;"
>
<span v-if="product.photoUploading">
<LoadingSpinner size="small" class="me-1" /> Uploading…
</span>
<span v-else>
<i class="fas fa-camera me-1"></i> Add Photo
</span>
</label>
<div v-else class="d-flex align-items-center gap-2 flex-grow-1">
<img
:src="`/RequestData/File/${product.photoHash}`"
class="rounded-2 border"
style="width:48px;height:48px;object-fit:cover;"
alt="Product photo"
/>
<button
class="btn btn-link btn-sm text-danger p-0"
@click="removeLeafPhoto(index)"
title="Remove photo"
>
<i class="fas fa-times-circle"></i>
</button>
</div>
<input
:id="`photo-input-${index}`"
type="file"
accept="image/*"
class="d-none"
@change="(e) => handleLeafPhoto(index, e)"
/>
</div>
```
Use `/RequestData/File/${hashkey}` for the preview (same path used by `FileList::resolvedUrl()` fallback).
### Step 4 — Replace category `<input list>` with `<select>`
Replace:
```html
<input
v-model="product.category"
type="text"
list="leaf-categories"
class="form-control form-control-sm"
placeholder="e.g. Vegetables"
>
```
With:
```html
<select v-model="product.category" class="form-select form-select-sm">
<option value="">— Category —</option>
<option v-for="cat in categories" :key="cat" :value="cat">{{ cat }}</option>
</select>
```
Also remove the `<datalist id="leaf-categories">` element at the bottom of the template since it is no longer needed.
### Step 5 — Include `photourl` in the save payload
In `saveProducts()`, update the `source === 'new'` branch of the payload map:
```js
{
source: 'new',
name: p.name,
price: p.price,
available: p.available,
unitname: p.unitname,
description: p.description,
category: p.category,
subcategory: p.subcategory,
barcode: p.barcode,
photourl: p.photoHash ? [p.photoHash] : [],
}
```
### Step 6 — Update `BatchController::batchCreateProducts()` to persist photourl
In `app/Http/Controllers/Market/BatchController.php`, in the `source === 'new'` validator rules, add:
```php
'photourl' => 'nullable|array',
'photourl.*' => 'nullable|string',
```
In `Product::create([...])`, add:
```php
'photourl' => $productData['photourl'] ?? [],
```
---
## Definition of Done checklist
- [ ] New leaf cards show an "Add Photo" button that opens a file picker
- [ ] Selecting a photo uploads it and shows a thumbnail preview with a remove ×
- [ ] Loading spinner shown during upload; button disabled while uploading
- [ ] `photoHash` is included in the save payload as `photourl: [hash]`
- [ ] Backend accepts and stores `photourl` on the new product
- [ ] Category field is a `<select>` dropdown populated with categories from the API
- [ ] Selecting a category from the dropdown sets `product.category` correctly
- [ ] `<datalist id="leaf-categories">` removed from template
- [ ] Existing fields (subcategory, barcode, description) unchanged
- [ ] Dark-mode styles still apply (form-select already covered by existing dark-mode scoped rule)
- [ ] `source === 'existing'` leaves are unaffected (no photo upload shown, category not editable)
- [ ] Build passes (`npm run build`)