# Gyoza

> Inventory and issue tracking for an arcade machine restoration organization (HFSPlay).

A single-page application built with **Quasar Framework (Vue 3)** on the frontend and **PocketBase** as the backend. Gyoza replaces a previous Firebase-based version and provides a comprehensive system for cataloging, managing, and tracking items (machines, accessories) with per-item event history and task management.

---

## Architecture

```
┌──────────────────────────────────────────────────────┐
│                  Gyoza Architecture                   │
├──────────────────────────────────────────────────────┤
│                                                      │
│  Browser (SPA)                                       │
│  ┌──────────────────────────────────────────────┐    │
│  │  Quasar Framework (Vue 3 + Vite)              │    │
│  │  ├── Vuex Store (core module)                 │    │
│  │  ├── Vue Router (hash mode)                   │    │
│  │  ├── Quasar Plugins (Dialog, Notify, etc.)    │    │
│  │  ├── PocketBase JS SDK (superuser auth)       │    │
│  │  └── QRCode / Image Compression              │    │
│  └──────────────────────────────────────────────┘    │
│         │                                            │
│         │ HTTP REST API                              │
│         ▼                                            │
│  ┌──────────────────────────────────────────────┐    │
│  │  PocketBase Server (Go binary)                │    │
│  │  ├── SQLite Database (pb_data/data.db)        │    │
│  │  │   ├── categories                           │    │
│  │  │   ├── stock (unique ref, short_id)         │    │
│  │  │   └── history (ref index, file uploads)    │    │
│  │  ├── File Storage (history images)            │    │
│  │  └── pb_migrations (schema versioning)        │    │
│  └──────────────────────────────────────────────┘    │
│                                                      │
│  Auth: SHA-256 credential check (UI gate) +          │
│  PocketBase superuser for all API calls              │
│                                                      │
└──────────────────────────────────────────────────────┘
```

---

## Tech Stack

| Layer | Technology |
|---|---|
| **Framework** | Quasar v2.16 (Vue 3.4, Composition & Options API) |
| **Routing** | Vue Router 4 (hash mode) |
| **State** | Vuex 4 |
| **Backend** | PocketBase v0.26.1 (Go binary, SQLite, REST API, file storage) |
| **Storage** | SQLite (`pb_data/data.db`) + file storage for images |
| **Build** | Vite via `@quasar/app-vite` |
| **Lint** | ESLint (Standard + Vue 3 Essential) |
| **Key Libs** | `pocketbase` SDK, `qrcode`, `browser-image-compression`, `axios` |

---

## Features

### Authentication
- Custom frontend login with SHA-256 credential verification
- Credentials persisted in `localStorage`
- Router guard redirects unauthenticated users to login
- Separate from PocketBase auth (which uses hardcoded superuser credentials for all API calls)

### Dashboard (`/dashboard`)
- **Global Search** — search items by reference (`ref`) or name (minimum 3 characters). Query persists in the URL.
- **Activity Feed** — last 15 history entries across all items, with relative timestamps and links to the associated item.

### Category Browsing (`/category/:categoryCode`)
- Paginated, sortable table of all items in a category
- Columns: Reference, Name, Condition, Working, In Stock, Owner, Deleted
- Toggle to show/hide soft-deleted items
- In-table search with URL-persisted query
- Condition state shown as a colored chip:
  - **Neuf** (green), **Excellent** (green), **Bon** (lime), **Moyen** (orange), **Abimé** (red), **Inutilisable** (black)
- "Add Item" button per category in the sidebar generates the next sequential reference (e.g., `CONS-0004`)

### Item Detail (`/item/:itemRef`)
- **Edit fields:** Name, Owner, Cosmetic Condition, Working (true/false/null), In Stock (available), Deleted (soft delete), Comment
- **QR Code generation** — generates a QR code encoding the item's URL, previewable in a dialog and printable in a new window
- **History timeline** — chronological list of all events/comments for the item:
  - Add new entries as either **Info** (comment) or **Task** (issue)
  - Upload photos with client-side compression (browser-image-compression, max 1MB, 2000px)
  - Click an entry to see full details, photos, and metadata
  - Mark tasks as resolved (records `solved_at` and `solved_by`)
  - Delete entries with a confirmation dialog

### Item Detail (short ID) (`/:shortId`)
- Short numeric IDs can be used to access an item directly (extracted from QR codes)
- Automatically resolves the `short_id` to the full `ref` and redirects

### Tasks (`/tasks`)
- Consolidated view of all issue-type history entries across all items
- Shows item context (category, name, reference) per task
- Mark tasks as done directly
- Delete tasks
- Navigate to the associated item

### Print Settings (`/print-settings`)
- Preview layout with configurable columns and rows (grid)
- Adjustable gap between cells
- A4 print page generation with QR codes + item names for the latest 24 items
- Generates batched QR code sheets in a printable HTML page

### Sidebar Navigation
- Category list with item counts (the latest sequential reference number per category)
- Quick-add button per category
- **Utilitaires** section: Tasks and Print shortcuts

### User Identity
- On first visit after login, the app prompts for a display name (pseudo)
- Stored in `localStorage` and displayed in the header
- Used as the author for history entries and task resolutions

---

## Database Schema

### `categories` collection
| Field | Type | Description |
|---|---|---|
| `id` | text (PK) | Auto-generated |
| `code` | text | Short code (e.g., `CONS`, `ACC`) |
| `name` | text | Display name (e.g., `Console`, `Accessoire`) |
| `created` / `updated` | autodate | Timestamps |

### `stock` collection
| Field | Type | Description |
|---|---|---|
| `id` | text (PK) | Auto-generated |
| `ref` | text (unique) | Sequential reference (e.g., `CONS-0001`) |
| `short_id` | number | Numeric short ID for QR codes |
| `name` | text | Item name |
| `type` | text | Category name (e.g., `Console`) |
| `state` | text | Condition: `Neuf`, `Excellent`, `Bon`, `Moyen`, `Abimé`, `Inutilisable` |
| `working` | bool/null | Functional status (true/false/null) |
| `available` | bool | Currently in stock |
| `owner` | text | Owner (default: `HFS`) |
| `comment` | text | Free-form notes |
| `deleted` | bool | Soft delete flag |
| `created` / `updated` | autodate | Timestamps |

### `history` collection
| Field | Type | Description |
|---|---|---|
| `id` | text (PK) | Auto-generated |
| `ref` | text (indexed) | References `stock.ref` |
| `type` | select | `comment` or `issue` |
| `text` | text | Event description |
| `user` | text | Display name of the author |
| `date` | date | Event date |
| `image_file` | file | Uploaded photo (thumb: 300x300f, max 1 file) |
| `image` | text | Legacy Firebase image name |
| `solved_at` | date | When the issue was resolved |
| `solved_by` | text | Who resolved the issue |
| `created` / `updated` | autodate | Timestamps |

---

## Getting Started

### Prerequisites
- **Node.js** >= 16
- **pnpm** (recommended) or npm
- PocketBase binary (place `pocketbase` in `./pocketbase/`)

### Installation

```bash
# Install dependencies
pnpm install

# Start PocketBase (in a separate terminal)
./pocketbase/pocketbase serve --dir ./pocketbase/pb_data

# Start the development server
pnpm dev
```

The frontend runs on `http://localhost:9000` and connects to PocketBase at `http://127.0.0.1:8090` in development, or `https://stock.hfsplay.fr` in production (see `src/boot/pocketbase.js`).

### Production Build

```bash
pnpm build
```

Output goes to `dist/spa/`.

---

## Scripts

Data migration scripts are located in `scripts/`:

| Script | Purpose |
|---|---|
| `import_categories.js` | Import categories from legacy Firebase JSON backup |
| `import_stock.js` | Import stock items from legacy Firebase JSON backup |
| `import_history.js` | Import history entries from legacy Firebase JSON backup |
| `import_history_files.js` | Upload historical images to PocketBase storage (with sharp compression) |
| `renumerate.js` | Re-assign sequential `short_id` values to all stock items |

---

## Project Structure

```
gyoza2/
├── pocketbase/              # PocketBase backend
│   ├── pb_data/             # SQLite DB + storage (gitignored)
│   └── pb_migrations/       # Schema migration files (9 migrations)
├── src/
│   ├── boot/                # Startup files (pocketbase.js, helper.js)
│   ├── components/          # Feed.vue, GlobalSearch.vue
│   ├── css/                 # Global styles + Geist font
│   ├── layouts/             # DefaultLayout.vue, FullscreenLayout.vue
│   ├── pages/               # Dashboard, StockItem, StockItems, TasksPage, PrintSettings, Index
│   ├── router/              # Route definitions (hash mode)
│   └── store/core/          # Vuex module (state, actions, mutations)
├── scripts/                 # Data migration utilities
├── public/                  # Static assets (icons, fonts, logos)
├── quasar.config.js         # Quasar build configuration
└── package.json
```

---

## Key Design Decisions

- **Superuser-as-client:** PocketBase SDK authenticates as a superuser in the browser. All API calls have full privileges; the frontend login is a UI-level gate only. This simplifies permissions but the superuser credentials are hardcoded in source files.
- **Full data load:** All collections are loaded into Vuex on startup via `getFullList()`. There is currently no real-time subscription or incremental sync.
- **Hash routing:** The app uses `hash` mode for Vue Router, meaning all URLs are client-side (`/#/dashboard`, etc.). This avoids server-side routing requirements.
- **Soft delete:** Items are never truly deleted from the database — the `deleted` boolean flag hides them from normal views.
- **French UI:** The entire user interface is in French.

---

## Roadmap & Technical Debt

### Security (high priority)
- [ ] **Remove hardcoded superuser credentials** from `src/boot/pocketbase.js` — the password is exposed in every browser. Move to server-side proxy or use PocketBase's built-in auth with proper user records and row-level collection rules.
- [ ] **Replace the dual-auth system** — the SHA-256 credential check on the frontend is a UI-only gate while PocketBase superuser creds power the API. Unify into a single PocketBase user auth flow so row-level security can be enforced.
- [ ] **Move credential hash to environment variables** — the hardcoded hash in `src/boot/helper.js` should not live in source.

### Scalability & Performance
- [ ] **Replace client-side filtering/pagination with server-side queries** — `StockItems.vue` loads all records then paginates in the browser. Switch to PocketBase's `getList()` with `page`/`perPage` params and server-side `filter`/`sort` strings.
- [ ] **Avoid full `getFullList()` on every navigation** — `loadAppData()` fetches all collections each time a page mounts. Implement caching or differential loading.
- [ ] **Re-enable real-time subscriptions** — there is commented-out Firebase realtime code in `DefaultLayout.vue`. PocketBase supports realtime SSE subscriptions; use `pb.collection('stock').subscribe('*', ...)` and `pb.collection('history').subscribe('*', ...)` to incrementally update Vuex.

### Architecture & Refactoring
- [ ] **Renamme Vuex state keys from `firebase`** — the store still uses `state.firebase.categories`, `state.firebase.stock`, `state.firebase.history` as legacy names. Rename to something meaningful (e.g., `state.data`, `state.collections`).
- [ ] **Add a database relation between `history.ref` and `stock`** — currently `history.ref` is a plain text field with only an index. Convert to a PocketBase relation field for proper referential integrity and expand queries.
- [ ] **Clean up unused boilerplate files** — `EssentialLink.vue`, `IndexPage.vue`, `Error404.vue`, `ErrorNotFound.vue`, `MainLayout.vue`, `store/module-example/`.
- [ ] **Add proper error handling on all API calls** — most `dispatch` calls in components have `.then()` without a `.catch()`. Network failures or API errors will fail silently.
- [ ] **Add TypeScript support** — the project already has `types.d.ts`, `store-flag.d.ts`, and `jsconfig.json`. Migrate incrementally to `lang="ts"` in Vue SFCs.

### Features
- [ ] **Editable categories** — categories are currently imported via script and cannot be managed through the UI.
- [ ] **Batch print QR codes** with selectable items and configurable grid layout (Print Settings page currently disabled in sidebar).
- [ ] **Resolvable comments** — allow marking non-issue history entries as addressed/resolved.
- [ ] **Bulk operations** — multi-select items for batch edit (e.g., bulk owner change, bulk state update).
- [ ] **Export inventory to CSV/PDF** — generate reports of current stock.
- [ ] **Search by `issue` status** — filter tasks page by open/closed state.
- [ ] **Image gallery per item** — the history list already shows photo thumbnails; add a dedicated gallery view for an item's images.
- [ ] **Item duplication** — clone an existing item (without history) as a quick way to add similar entries.
- [ ] **Remote backup** — auto-backup PocketBase SQLite database and storage files to MinIO or S3.

### UI / UX
- [ ] **Fix Print Settings page** — the sidebar link has `disable` and the page has no way to select which items to print.
- [ ] **Persistent sidebar state** — `leftDrawer` opens by default on every page load. Persist the open/closed preference in `localStorage`.
- [ ] **Loading/empty states** — many views show raw empty arrays during initial load rather than skeleton loaders or empty-state placeholders.
- [ ] **Mobile responsiveness** — the 2-column layout on `StockItem.vue` stacks to a single column below `lg` breakpoint, but the table and sidebar could use further mobile polish.
- [ ] **Fix style inconsistencies** — various styling issues noted in commit history and original TODO.
