2026-07-26 00:23:28 +02:00
2025-03-20 09:07:33 +01:00
2025-03-20 09:07:33 +01:00
2026-07-26 00:23:28 +02:00
2025-06-27 15:49:04 +02:00
2026-07-26 00:23:28 +02:00
2025-03-20 09:07:33 +01:00
2025-03-20 15:54:16 +01:00
2025-04-18 13:29:15 +02:00
2025-04-16 21:38:56 +02:00
2025-03-20 09:07:33 +01:00
2025-03-20 09:07:33 +01:00
2025-03-20 09:07:33 +01:00
2026-03-15 13:40:41 +01:00
2025-06-27 15:35:31 +02:00
2025-03-20 09:07:33 +01:00
2026-07-26 00:23:28 +02:00

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

# 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 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 queriesStockItems.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 navigationloadAppData() 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 filesEssentialLink.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 stateleftDrawer 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.
Description
New gyoza, with new backend and up to date frontend
Readme 12 MiB
Languages
Vue 67.3%
JavaScript 29.1%
SCSS 2.7%
HTML 0.9%