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