initial: bootstrap from BukidBountyApp base

This commit is contained in:
Jonathan Sykes
2026-06-06 18:43:00 +08:00
commit eb4a5731fb
5674 changed files with 160857 additions and 0 deletions

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