1a0431366f
Aligns the repo with the python-project-spec-design.md template chosen for V2.0.0. Big move, no logic changes. The 3 pre-existing test failures (test_recipes::test_update_recipe, test_recipes:: test_recipe_versioning, test_tasks::test_reorder_tasks, plus the client test_save_measurement_proxy) survive unchanged. Layout changes - server/ -> src/backend/ - server/middleware/ -> src/backend/api/middleware/ - server/routers/ -> src/backend/api/routers/ - server/models/ -> src/backend/models/orm/ - server/schemas/ -> src/backend/models/api/ - server/uploads/ -> uploads/ (project root, mounted volume) - server/tests/ -> src/backend/tests/ - client/ -> src/frontend/flask_app/ (Flask kept; React deroga is documented in CLAUDE.md, justified by tablet UX, USB caliper/barcode workflow and Fabric.js integration) Tooling - pyproject.toml: monorepo with [project] core deps and optional-dependencies server / client / dev. Replaces both server/requirements.txt and client/requirements.txt. - uv.lock + .python-version (3.11) committed for reproducible builds. - Dockerfile (root, backend) and Dockerfile.frontend rewritten to use uv sync --frozen --no-dev --extra server|client; legacy Dockerfiles preserved as Dockerfile.legacy for reference but excluded from build context via .dockerignore. - docker-compose.dev.yml + docker-compose.yml: build context now ".", dockerfile pointing to the root files. Code adjustments forced by the move - Every "from config|database|models|schemas|services|routers|middleware import ..." rewritten to its src.backend.* equivalent (50+ files including indented inline imports inside test bodies). - src/backend/migrations/env.py: insert project root into sys.path so alembic can resolve src.backend.* imports regardless of cwd. - src/backend/config.py: env_file ../../.env (was ../.env), upload_path resolves project root via parents[2]. - src/backend/tests/conftest.py + tests: import ... from src.backend.* instead of bare names; old per-directory pytest.ini files removed in favor of root pyproject.toml [tool.pytest.ini_options]. - .gitignore: uploads/ at root, src/frontend/flask_app/static/css/ tailwind.css path; .dockerignore tightened. - CLAUDE.md: rewrote sections "Layout del repository", "Comandi di Sviluppo", "Database & Migrations", "Test", "i18n", and all path references throughout the architecture sections. Verified - uv lock resolves 77 packages; uv sync --extra server --extra client --extra dev installs cleanly. - uv run pytest: 171 passed, 4 pre-existing failures. - uv run alembic -c src/backend/migrations/alembic.ini check loads config and metadata (errors only on the absent local MySQL). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
160 lines
7.7 KiB
HTML
160 lines
7.7 KiB
HTML
<!--
|
|
Barcode Scanner Modal Component
|
|
Camera-based barcode/QR code scanner
|
|
Include in pages where barcode scanning is needed
|
|
-->
|
|
<div x-data="barcodeScanner()"
|
|
@barcode-use.window="stopScan(); clear()"
|
|
x-init="$watch('scanning', val => console.log('Scanning:', val))"
|
|
x-cloak>
|
|
|
|
<!-- Trigger button (styled slot - customize per use case) -->
|
|
<slot name="trigger">
|
|
<button @click="startScan()"
|
|
type="button"
|
|
class="inline-flex items-center gap-2 px-4 py-2.5 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-600 rounded-lg text-sm font-medium text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors duration-200 shadow-sm hover:shadow-md">
|
|
|
|
<!-- Barcode icon -->
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
|
|
</svg>
|
|
<span>{{ _('Scansiona Barcode') }}</span>
|
|
</button>
|
|
</slot>
|
|
|
|
<!-- Modal overlay -->
|
|
<div x-show="scanning || result"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4"
|
|
style="display: none;">
|
|
|
|
<div @click.outside="scanning && !result ? null : (stopScan(), clear())"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
class="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl max-w-md w-full overflow-hidden">
|
|
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center px-6 py-4 border-b border-slate-200 dark:border-slate-700">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
|
|
<svg class="w-5 h-5 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
|
|
{{ _('Scansiona Barcode') }}
|
|
</h3>
|
|
</div>
|
|
|
|
<button @click="stopScan(); clear()"
|
|
type="button"
|
|
class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors p-1 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="p-6">
|
|
|
|
<!-- Camera preview container -->
|
|
<div x-show="scanning && !result"
|
|
class="relative rounded-xl overflow-hidden bg-slate-900 aspect-square mb-4">
|
|
|
|
<!-- Scanner element -->
|
|
<div id="barcode-reader" class="w-full h-full"></div>
|
|
|
|
<!-- Scanning indicator -->
|
|
<div class="absolute inset-0 pointer-events-none">
|
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
<div class="w-64 h-64 border-2 border-white/50 rounded-lg"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Instruction overlay -->
|
|
<div class="absolute bottom-0 inset-x-0 bg-gradient-to-t from-black/80 to-transparent p-4">
|
|
<p class="text-white text-sm text-center font-medium">
|
|
{{ _('Inquadra il codice a barre nel riquadro') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success result -->
|
|
<div x-show="result"
|
|
x-transition
|
|
class="text-center py-8">
|
|
|
|
<!-- Success icon -->
|
|
<div class="w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg class="w-8 h-8 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<p class="text-sm text-slate-600 dark:text-slate-400 mb-3">
|
|
{{ _('Codice rilevato') }}
|
|
</p>
|
|
|
|
<div class="bg-slate-50 dark:bg-slate-900/50 rounded-lg p-4 mb-4">
|
|
<p class="font-mono text-xl font-bold text-primary break-all" x-text="result"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error message -->
|
|
<div x-show="error"
|
|
x-transition
|
|
class="mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-red-600 dark:text-red-400 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<p class="text-sm text-red-800 dark:text-red-200" x-text="error"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action buttons -->
|
|
<div class="flex gap-3">
|
|
<button x-show="result"
|
|
@click="$dispatch('barcode-use', { code: result }); stopScan(); clear()"
|
|
type="button"
|
|
class="flex-1 px-4 py-2.5 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-colors duration-200 shadow-sm hover:shadow-md">
|
|
{{ _('Usa Codice') }}
|
|
</button>
|
|
|
|
<button x-show="scanning && !result"
|
|
@click="stopScan()"
|
|
type="button"
|
|
class="flex-1 px-4 py-2.5 bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium rounded-lg transition-colors duration-200">
|
|
{{ _('Ferma') }}
|
|
</button>
|
|
|
|
<button x-show="!scanning || result"
|
|
@click="stopScan(); clear()"
|
|
type="button"
|
|
class="flex-1 px-4 py-2.5 bg-slate-100 dark:bg-slate-700 hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-300 font-medium rounded-lg transition-colors duration-200">
|
|
{{ _('Chiudi') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.BARCODE_I18N = {
|
|
scanner_lib_not_loaded: "{{ _('Libreria scanner non caricata') }}",
|
|
no_camera_available: "{{ _('Nessuna fotocamera disponibile') }}",
|
|
camera_access_error: "{{ _('Impossibile accedere alla fotocamera') }}"
|
|
};
|
|
</script>
|
|
</div>
|