initial: bootstrap from BukidBountyApp base
This commit is contained in:
163
.claude/plans/0f8a3ffd129c8d6000dfd18d01432000-complete.md
Normal file
163
.claude/plans/0f8a3ffd129c8d6000dfd18d01432000-complete.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
task: Fix "Open POS" on HomeStoreOwner and HomeShared (StoreManager) to pass the store hashkey to PosMain. If user has one store, navigate directly with its hashkey. If multiple stores, show a store selection modal first.
|
||||
cycles: 5
|
||||
context: true
|
||||
private: false
|
||||
started: 2026-05-16T00:00:00Z
|
||||
finished: 2026-05-16T00:01:00Z
|
||||
---
|
||||
|
||||
## files
|
||||
- resources/js/Pages/Fragments/Home/HomeStoreOwner.vue [lines 30-32, 115-123] — defines balanceFooterItems with pagename:'PosMain' (no target prop), handleItemClick navigates without store hashkey
|
||||
- resources/js/Pages/Fragments/Home/HomeShared.vue — StoreManager home; no POS button at all; needs Open POS added with same multi-store logic
|
||||
- resources/js/Pages/Home.vue [lines 62-69] — routes isStoreOwner→HomeStoreOwner, isStoreManager→HomeShared
|
||||
- resources/js/Pages/PosMain.vue [lines 18-21] — expects `target` prop (store or session hashkey) and `access_key` prop; without `target` usePosSession cannot initialize the store
|
||||
- resources/js/composables/Market/usePosSession.js [lines 25-47] — initialize() reads `props.target` as hashkey; if null, storeHash stays null and store products never load
|
||||
- app/Http/Controllers/Market/StoreController.php [lines 1143-1206] — `listStoresForCurrentUser()` returns [{hashkey, name, category, role}] for owner+manager
|
||||
- routes/web.php [line 484] — POST /ListStores/MyStores/data → StoreController@listStoresForCurrentUser (auth+module:stores middleware)
|
||||
|
||||
## steps
|
||||
|
||||
### Step 1 — HomeStoreOwner.vue: fetch stores and smart-navigate on "Open POS"
|
||||
|
||||
1. Add `axios` is already imported. Add a `loadingStores` ref.
|
||||
2. Replace the `balanceFooterItems` "Open POS" item — keep `pagename: 'PosMain'` but add `action: 'openPos'` to differentiate from direct navigation.
|
||||
3. Write an `openPos()` async function:
|
||||
```
|
||||
const openPos = async () => {
|
||||
try {
|
||||
const { data: stores } = await axios.post('/ListStores/MyStores/data', {});
|
||||
if (!stores || stores.length === 0) {
|
||||
modal.quickDismiss({ title: 'No Store Found', body: 'You have no active stores assigned to your account.' });
|
||||
return;
|
||||
}
|
||||
if (stores.length === 1) {
|
||||
navigate({ page: 'PosMain', props: { target: stores[0].hashkey } });
|
||||
return;
|
||||
}
|
||||
// Multiple stores: show selection modal
|
||||
showStoreSelectModal(stores);
|
||||
} catch (e) {
|
||||
modal.quickDismiss({ title: 'Error', body: 'Could not load your stores. Please try again.' });
|
||||
}
|
||||
};
|
||||
```
|
||||
4. Write `showStoreSelectModal(stores)` using `modal.open()` with a rendered list of store buttons (use `h()` from vue). On store click: `modal.hideModal()` then `navigate({ page: 'PosMain', props: { target: store.hashkey } })`.
|
||||
5. In `handleItemClick`, intercept `item.action === 'openPos'` before the pagename check and call `openPos()`.
|
||||
6. Update the `balanceFooterItems` entry to use `action: 'openPos'` instead of/alongside `pagename: 'PosMain'`.
|
||||
|
||||
**Exact change to balanceFooterItems (line 30):**
|
||||
```js
|
||||
// Before:
|
||||
{ title: 'Open POS', icon: '...', pagename: 'PosMain' },
|
||||
// After:
|
||||
{ title: 'Open POS', icon: '...', action: 'openPos' },
|
||||
```
|
||||
|
||||
**Exact change to handleItemClick (lines 115-123):**
|
||||
```js
|
||||
const handleItemClick = async (item) => {
|
||||
if (item?.action === 'chooseCreateStoreMode') {
|
||||
openCreateStoreChooser();
|
||||
return;
|
||||
}
|
||||
if (item?.action === 'openPos') {
|
||||
await openPos();
|
||||
return;
|
||||
}
|
||||
if (item?.pagename) {
|
||||
navigate({ page: item.pagename, props: { data: item.pagestring || '' } });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Step 2 — HomeShared.vue: add Open POS button for StoreManager role
|
||||
|
||||
1. Import `computed` (already imported), `axios`, `useModal`, and `h` from vue.
|
||||
2. Check if current role is `STORE_MANAGER` (use `role` from `useAuth()`).
|
||||
3. Add an `openPos` function identical in logic to Step 1.
|
||||
4. Add `showStoreSelectModal(stores)` function identical to Step 1.
|
||||
5. Add a computed `posServices` that returns an "Open POS" button only when `role.value === UserTypes.STORE_MANAGER` (or always show for manager; the page is already role-gated).
|
||||
6. In the template, add `<ServiceButtonGrid :items="posItems" @item-click="handlePosClick" />` or extend `services` to include the POS button — prefer extending `services` with `action: 'openPos'` for managers.
|
||||
7. Alternatively, add "Open POS" as a `quickActionsItems` entry with roles `[UserTypes.STORE_MANAGER]` and handle it in `handleItemClick` with the same `openPos` logic.
|
||||
|
||||
**Simplest approach:** extend `quickActionsItems` with:
|
||||
```js
|
||||
{
|
||||
text: 'Open POS',
|
||||
action: 'openPos',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/5b5ef88c0ad1.svg',
|
||||
roles: [UserTypes.STORE_MANAGER],
|
||||
},
|
||||
```
|
||||
And update `handleItemClick` to check `item.action === 'openPos'`.
|
||||
|
||||
### Step 3 — Verify no backend changes needed
|
||||
|
||||
The endpoint `POST /ListStores/MyStores/data` already:
|
||||
- Returns `[{hashkey, name, category, role}]` for the current user's stores (owner or manager)
|
||||
- Has `auth` middleware (user must be logged in)
|
||||
- Covers both STORE_OWNER and STORE_MANAGER via the query in `listStoresForCurrentUser()`
|
||||
|
||||
No backend changes needed.
|
||||
|
||||
## context
|
||||
|
||||
**HomeStoreOwner.vue — balanceFooterItems (line 30):**
|
||||
```js
|
||||
const balanceFooterItems = ref([
|
||||
{ title: 'Open POS', icon: '...svg', pagename: 'PosMain' }, // BUG: no target/hashkey
|
||||
{ title: 'My Stores', icon: '...bin', pagename: 'ManageStoresAdmin' },
|
||||
]);
|
||||
```
|
||||
|
||||
**HomeStoreOwner.vue — handleItemClick (lines 115-123):**
|
||||
```js
|
||||
const handleItemClick = (item) => {
|
||||
if (item?.action === 'chooseCreateStoreMode') {
|
||||
openCreateStoreChooser();
|
||||
return;
|
||||
}
|
||||
if (item?.pagename) {
|
||||
navigate({ page: item.pagename, props: { data: item.pagestring || '' } });
|
||||
}
|
||||
};
|
||||
```
|
||||
→ Navigates to `PosMain` without `target` prop. `usePosSession.initialize()` then has `props.target = null`, `storeHash` stays null, products never load, session can't start.
|
||||
|
||||
**PosMain.vue props (lines 18-21):**
|
||||
```js
|
||||
const props = defineProps({
|
||||
target: { type: String, default: null }, // Session hashkey
|
||||
access_key: { type: String, default: null },
|
||||
});
|
||||
```
|
||||
|
||||
**usePosSession.js initialize() (lines 25-47):**
|
||||
```js
|
||||
const hashkey = props.target; // null when coming from HomeStoreOwner
|
||||
if (hashkey) {
|
||||
await posStore.loadSession(hashkey, accessKey);
|
||||
// ...
|
||||
}
|
||||
await posStore.fetchProducts(accessKey, storeHash.value); // storeHash is null
|
||||
```
|
||||
|
||||
**StoreController@listStoresForCurrentUser returns (lines 1197-1202):**
|
||||
```php
|
||||
return [
|
||||
'hashkey' => $store->hashkey,
|
||||
'name' => $store->name,
|
||||
'category' => $store->category,
|
||||
'role' => $role, // 'owner' or 'manager'
|
||||
];
|
||||
```
|
||||
|
||||
**HomeShared.vue — StoreManager currently has NO POS button.** It only shows Market, My Wallet, Shipments in `services` and generic quickActions. The store manager use-case is identical to store owner: fetch their stores, navigate with hashkey.
|
||||
|
||||
**useModal available functions:** `open({ title, body, footer })`, `quickDismiss({ title, body })`, `yesNoModal(...)`, `hideModal()`. Use `open()` with `h()` to render store list.
|
||||
|
||||
## notes
|
||||
- dictionary: none
|
||||
- linters: eslint:no, phpcs:no, tsc:no
|
||||
- constraints: Use existing `/ListStores/MyStores/data` POST endpoint (already exists, no new backend route needed). Modal store list should show store name and category. Use `h()` (already importable from vue) to build modal body for store selection. The `useModal` composable's `open()` accepts a Vue render function or component as `body`.
|
||||
Reference in New Issue
Block a user