feat: initial commit — local services (backend + manager dashboard + waiter PWA)

Includes all work to date:
- local_backend: FastAPI backend with products, orders, tables, shifts, cloud sync
- manager_dashboard: React manager UI with product/category management, reports, settings
- waiter_pwa: React PWA for waiter devices
- Category reparent endpoint and UI
- Waiter domain: local_ip sent on heartbeat, waiter_domain persisted from cloud response
- QR code modal in AppInfoTab for waiter domain
- Product form: number input spinners removed, category pre-selected on new product
- Category row: count badge moved to far right

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:04:38 +03:00
commit 8ba8c95ecd
209 changed files with 48017 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import db from '../db/posdb'
import client from '../api/client'
/**
* Queue an emergency payment locally.
* Called in Emergency Mode when the server is unreachable.
*/
export async function queueOfflinePayment({ orderId, itemIds, paymentMethod }) {
const uuid = crypto.randomUUID()
await db.offline_payments.add({
uuid,
orderId,
itemIds,
paymentMethod,
offlineAt: new Date().toISOString(),
synced: 0,
isDuplicate: 0,
})
return uuid
}
/**
* Flush all unsynced offline payments to the server.
* Called when the server comes back online.
* Returns a summary of { synced, duplicates, failed }.
*/
export async function flushOfflinePayments() {
// Boolean is not a valid IndexedDB key — load all and filter in JS
const all = await db.offline_payments.toArray()
const pending = all.filter(p => !p.synced)
const results = { synced: 0, duplicates: 0, failed: 0 }
for (const payment of pending) {
try {
const res = await client.post(`/api/orders/${payment.orderId}/pay-offline`, {
uuid: payment.uuid,
item_ids: payment.itemIds,
payment_method: payment.paymentMethod,
offline_at: payment.offlineAt,
})
const isDuplicate = res.data.is_duplicate
await db.offline_payments.update(payment.localId, {
synced: 1,
isDuplicate: isDuplicate ? 1 : 0,
})
isDuplicate ? results.duplicates++ : results.synced++
} catch {
results.failed++
}
}
return results
}
/**
* Count unsynced pending payments (to show badge / warning).
*/
export async function pendingPaymentCount() {
const all = await db.offline_payments.toArray()
return all.filter(p => !p.synced).length
}