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_atandsolved_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_idto the fullrefand 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
localStorageand 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
pocketbasein./pocketbase/)
Installation
# 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
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
hashmode 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
deletedboolean 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.jsshould not live in source.
Scalability & Performance
- Replace client-side filtering/pagination with server-side queries —
StockItems.vueloads all records then paginates in the browser. Switch to PocketBase'sgetList()withpage/perPageparams and server-sidefilter/sortstrings. - 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; usepb.collection('stock').subscribe('*', ...)andpb.collection('history').subscribe('*', ...)to incrementally update Vuex.
Architecture & Refactoring
- Renamme Vuex state keys from
firebase— the store still usesstate.firebase.categories,state.firebase.stock,state.firebase.historyas legacy names. Rename to something meaningful (e.g.,state.data,state.collections). - Add a database relation between
history.refandstock— currentlyhistory.refis 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
dispatchcalls 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, andjsconfig.json. Migrate incrementally tolang="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
issuestatus — 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
disableand the page has no way to select which items to print. - Persistent sidebar state —
leftDraweropens by default on every page load. Persist the open/closed preference inlocalStorage. - 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.vuestacks to a single column belowlgbreakpoint, but the table and sidebar could use further mobile polish. - Fix style inconsistencies — various styling issues noted in commit history and original TODO.
Description
Languages
Vue
67.3%
JavaScript
29.1%
SCSS
2.7%
HTML
0.9%