diff --git a/apps/client/public/chat.html b/apps/client/public/chat.html new file mode 100644 index 0000000..a419aa2 --- /dev/null +++ b/apps/client/public/chat.html @@ -0,0 +1,263 @@ + + + + + + Gigafibre + + + + +
+
+
+
Chargement...
+
+
+ + + + diff --git a/apps/client/src/App.vue b/apps/client/src/App.vue index eefeee9..a65f298 100644 --- a/apps/client/src/App.vue +++ b/apps/client/src/App.vue @@ -7,5 +7,10 @@ import { onMounted } from 'vue' import { useCustomerStore } from 'src/stores/customer' const store = useCustomerStore() -onMounted(() => store.init()) +onMounted(() => { + store.init().catch(() => { + // Auth not available (standalone portal mode) — continue as guest + console.info('[Portal] Auth unavailable — running in guest mode') + }) +}) diff --git a/apps/client/src/api/catalog.js b/apps/client/src/api/catalog.js new file mode 100644 index 0000000..632fc4d --- /dev/null +++ b/apps/client/src/api/catalog.js @@ -0,0 +1,11 @@ +const HUB = location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' + +const get = async (path) => { const r = await fetch(HUB + path); if (!r.ok) throw new Error(`Hub ${r.status}`); return r.json() } +const post = async (path, body) => { const r = await fetch(HUB + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!r.ok) throw new Error(`Hub ${r.status}`); return r.json() } + +export const fetchCatalog = () => get('/api/catalog') +export const submitOrder = (payload) => post('/api/checkout', payload) +export const getOrderStatus = (id) => get(`/api/order/${encodeURIComponent(id)}`) +export const searchAddresses = async (q, limit = 8) => (await post('/api/address-search', { q, limit })).results || [] +export const sendOTP = (identifier) => post('/api/otp/send', { identifier }) +export const verifyOTP = (identifier, code) => post('/api/otp/verify', { identifier, code }) diff --git a/apps/client/src/api/portal.js b/apps/client/src/api/portal.js index c3ebcf0..3108453 100644 --- a/apps/client/src/api/portal.js +++ b/apps/client/src/api/portal.js @@ -4,6 +4,8 @@ import { BASE_URL } from 'src/config/erpnext' async function apiGet (path) { const res = await authFetch(BASE_URL + path) if (!res.ok) throw new Error(`API ${res.status}: ${path}`) + const ct = (res.headers.get('content-type') || '') + if (!ct.includes('application/json')) throw new Error('Not JSON response for: ' + path) const data = await res.json() if (data.exc) throw new Error(data.exc) return data diff --git a/apps/client/src/composables/useAddressSearch.js b/apps/client/src/composables/useAddressSearch.js new file mode 100644 index 0000000..c9131ff --- /dev/null +++ b/apps/client/src/composables/useAddressSearch.js @@ -0,0 +1,49 @@ +import { ref, onUnmounted } from 'vue' +import { searchAddresses } from 'src/api/catalog' + +export function useAddressSearch (form) { + const query = ref('') + const results = ref([]) + const searching = ref(false) + const selected = ref(false) + let debounce = null + + function onInput (val) { + selected.value = false + Object.assign(form, { address: '', city: '', postalCode: '', latitude: null, longitude: null }) + if (debounce) clearTimeout(debounce) + if (!val || val.length < 3) { results.value = []; return } + debounce = setTimeout(async () => { + searching.value = true + try { results.value = await searchAddresses(val) } + catch { results.value = [] } + finally { searching.value = false } + }, 300) + } + + function selectResult (a) { + Object.assign(form, { + address: a.adresse_formatee || '', city: a.nom_municipalite || '', + postalCode: a.code_postal || '', latitude: a.latitude || null, + longitude: a.longitude || null, province: 'QC', + }) + query.value = a.adresse_formatee || '' + results.value = [] + selected.value = true + } + + function selectCustomerAddr (addr) { + Object.assign(form, { + address: addr.address || '', city: addr.city || '', + postalCode: addr.postal_code || '', latitude: addr.latitude || null, + longitude: addr.longitude || null, province: 'QC', + }) + query.value = addr.address || '' + selected.value = true + results.value = [] + } + + onUnmounted(() => { if (debounce) clearTimeout(debounce) }) + + return { query, results, searching, selected, onInput, selectResult, selectCustomerAddr } +} diff --git a/apps/client/src/composables/useOTP.js b/apps/client/src/composables/useOTP.js new file mode 100644 index 0000000..ae66168 --- /dev/null +++ b/apps/client/src/composables/useOTP.js @@ -0,0 +1,72 @@ +import { ref, nextTick } from 'vue' +import { sendOTP as apiSendOTP, verifyOTP as apiVerifyOTP } from 'src/api/catalog' +import { normalizePhone } from 'src/utils/format' + +export function useOTP (form, { onVerified } = {}) { + const inputRef = ref(null) + const identifier = ref('') + const step = ref('idle') // idle | verify + const code = ref('') + const sending = ref(false) + const verifying = ref(false) + const verified = ref(false) + const channel = ref('') + const error = ref('') + const addresses = ref([]) + + async function send () { + sending.value = true + error.value = '' + try { + const result = await apiSendOTP(identifier.value) + if (!result.found) { + error.value = 'Aucun compte trouvé avec cet identifiant.' + return false + } + step.value = 'verify' + channel.value = result.channel + nextTick(() => inputRef.value?.focus()) + return true + } catch (e) { + error.value = e.message || 'Erreur' + return false + } finally { sending.value = false } + } + + async function verify () { + verifying.value = true + error.value = '' + try { + const result = await apiVerifyOTP(identifier.value, code.value) + if (!result.valid) { + error.value = result.reason === 'expired' ? 'Code expiré. Renvoyez un nouveau code.' + : result.reason === 'wrong_code' ? 'Code invalide. Vérifiez et réessayez.' + : 'Vérification échouée.' + return false + } + verified.value = true + form.customer_id = result.customer_id + form.name = result.customer_name || form.name + if (result.phone) form.phone = normalizePhone(result.phone) + if (result.email) form.email = result.email + else if (identifier.value.includes('@')) form.email = identifier.value + if (result.addresses?.length) addresses.value = result.addresses + onVerified?.(result) + return true + } catch (e) { + error.value = e.message || 'Erreur' + return false + } finally { verifying.value = false } + } + + function reset () { + step.value = 'idle' + code.value = '' + verified.value = false + error.value = '' + addresses.value = [] + form.customer_id = null + } + + return { inputRef, identifier, step, code, sending, verifying, verified, channel, error, addresses, send, verify, reset } +} diff --git a/apps/client/src/data/catalog.js b/apps/client/src/data/catalog.js new file mode 100644 index 0000000..ddcbd15 --- /dev/null +++ b/apps/client/src/data/catalog.js @@ -0,0 +1,21 @@ +// ISP product catalog — extracted from CatalogPage to reduce component size +export const CATALOG = [ + { item_code: 'INT-100', item_name: 'Internet 100 Mbps', rate: 49.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install', description: 'Parfait pour le web et streaming HD', speed_down: 100, speed_up: 30 }, + { item_code: 'INT-300', item_name: 'Internet 300 Mbps', rate: 69.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install', description: 'Idéal pour familles et télétravail', speed_down: 300, speed_up: 100, popular: true }, + { item_code: 'INT-500', item_name: 'Internet 500 Mbps', rate: 89.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install', description: 'Pour les gros téléchargements et le gaming', speed_down: 500, speed_up: 200 }, + { item_code: 'INT-1000', item_name: 'Internet 1 Gbps', rate: 109.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install', description: 'La vitesse maximale pour les professionnels', speed_down: 1000, speed_up: 500 }, + { item_code: 'TEL-BASE', item_name: 'Téléphonie résidentielle', rate: 19.99, billing_type: 'Mensuel', service_category: 'Téléphonie', requires_visit: false, description: 'Appels illimités au Canada + afficheur' }, + { item_code: 'TEL-INTL', item_name: 'Téléphonie internationale', rate: 29.99, billing_type: 'Mensuel', service_category: 'Téléphonie', requires_visit: false, description: 'Appels illimités Canada + 60 destinations internationales' }, + { item_code: 'BDL-300-TEL', item_name: 'Bundle Internet 300 + Téléphonie', rate: 79.99, billing_type: 'Mensuel', service_category: 'Bundle', requires_visit: true, project_template_id: 'fiber_install', description: 'Internet rapide et téléphonie à prix réduit', speed_down: 300, speed_up: 100, bundle_includes: ['Internet 300 Mbps', 'Téléphonie résidentielle'] }, + { item_code: 'BDL-500-TEL-IPTV', item_name: 'Bundle Internet 500 + Téléphonie + IPTV', rate: 109.99, billing_type: 'Mensuel', service_category: 'Bundle', requires_visit: true, project_template_id: 'fiber_install', description: 'Le forfait complet pour toute la famille', speed_down: 500, speed_up: 200, popular: true, bundle_includes: ['Internet 500 Mbps', 'Téléphonie résidentielle', 'IPTV 120+ chaînes'] }, + { item_code: 'EQ-ROUTER-WIFI6', item_name: 'Routeur Wi-Fi 6', rate: 149.99, billing_type: 'Unique', service_category: 'Équipement', requires_visit: false, description: 'Routeur haute performance, couverture optimale' }, + { item_code: 'EQ-MESH-NODE', item_name: 'Noeud Wi-Fi Mesh', rate: 99.99, billing_type: 'Unique', service_category: 'Équipement', requires_visit: false, description: 'Étend la couverture Wi-Fi dans les grandes maisons' }, + { item_code: 'EQ-ONT', item_name: 'Terminal fibre optique (ONT)', rate: 0, billing_type: 'Unique', service_category: 'Équipement', requires_visit: true, project_template_id: 'fiber_install', description: 'Inclus avec tout abonnement Internet fibre' }, +] + +export const CATEGORY_COLORS = { Internet: 'indigo', 'Téléphonie': 'teal', Bundle: 'purple', 'Équipement': 'blue-grey' } + +export const TIME_SLOTS = [ + { label: 'Matin (8h - 12h)', value: 'AM' }, + { label: 'Après-midi (12h - 17h)', value: 'PM' }, +] diff --git a/apps/client/src/layouts/PortalLayout.vue b/apps/client/src/layouts/PortalLayout.vue index 163b3ac..d5bc820 100644 --- a/apps/client/src/layouts/PortalLayout.vue +++ b/apps/client/src/layouts/PortalLayout.vue @@ -10,6 +10,9 @@ {{ store.customerName }} + + + @@ -31,13 +34,13 @@ - -
+ +
- -
+ +
{{ store.error }}
@@ -52,19 +55,29 @@ + + diff --git a/apps/client/src/pages/CatalogPage.vue b/apps/client/src/pages/CatalogPage.vue new file mode 100644 index 0000000..7b089b7 --- /dev/null +++ b/apps/client/src/pages/CatalogPage.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/apps/client/src/pages/OrderSuccessPage.vue b/apps/client/src/pages/OrderSuccessPage.vue new file mode 100644 index 0000000..5a45283 --- /dev/null +++ b/apps/client/src/pages/OrderSuccessPage.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/apps/client/src/router/index.js b/apps/client/src/router/index.js index bb2cc9b..aad24ac 100644 --- a/apps/client/src/router/index.js +++ b/apps/client/src/router/index.js @@ -12,6 +12,9 @@ const routes = [ { path: 'tickets/:name', name: 'ticket-detail', component: () => import('pages/TicketDetailPage.vue') }, { path: 'messages', name: 'messages', component: () => import('pages/MessagesPage.vue') }, { path: 'me', name: 'account', component: () => import('pages/AccountPage.vue') }, + { path: 'catalogue', name: 'catalog', component: () => import('pages/CatalogPage.vue') }, + { path: 'panier', name: 'cart', component: () => import('pages/CartPage.vue') }, + { path: 'commande/confirmation', name: 'order-success', component: () => import('pages/OrderSuccessPage.vue') }, ], }, ] diff --git a/apps/client/src/stores/cart.js b/apps/client/src/stores/cart.js new file mode 100644 index 0000000..c787fdf --- /dev/null +++ b/apps/client/src/stores/cart.js @@ -0,0 +1,98 @@ +import { defineStore } from 'pinia' +import { ref, computed, watch } from 'vue' + +const STORAGE_KEY = 'gigafibre_cart' + +function loadFromStorage () { + try { + const raw = localStorage.getItem(STORAGE_KEY) + return raw ? JSON.parse(raw) : [] + } catch { return [] } +} + +export const useCartStore = defineStore('cart', () => { + const items = ref(loadFromStorage()) + const taxRate = 0.14975 // TPS 5% + TVQ 9.975% + + // Persist to localStorage on every change + watch(items, (val) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(val)) + }, { deep: true }) + + function addItem (product) { + const existing = items.value.find(i => i.item_code === product.item_code) + if (existing) { + existing.qty += 1 + } else { + items.value.push({ + item_code: product.item_code, + item_name: product.item_name, + rate: product.rate, + qty: 1, + billing_type: product.billing_type, + service_category: product.service_category, + requires_visit: product.requires_visit || false, + project_template_id: product.project_template_id || null, + description: product.description || '', + }) + } + } + + function removeItem (index) { + items.value.splice(index, 1) + } + + function updateQty (index, qty) { + if (qty < 1) { + removeItem(index) + return + } + items.value[index].qty = qty + } + + function clearCart () { + items.value = [] + } + + const itemCount = computed(() => + items.value.reduce((sum, i) => sum + i.qty, 0), + ) + + const onetimeTotal = computed(() => + items.value + .filter(i => i.billing_type !== 'Mensuel') + .reduce((sum, i) => sum + i.rate * i.qty, 0), + ) + + const recurringTotal = computed(() => + items.value + .filter(i => i.billing_type === 'Mensuel') + .reduce((sum, i) => sum + i.rate * i.qty, 0), + ) + + const subtotal = computed(() => onetimeTotal.value + recurringTotal.value) + + const taxAmount = computed(() => subtotal.value * taxRate) + + const grandTotal = computed(() => subtotal.value + taxAmount.value) + + const requiresVisit = computed(() => + items.value.some(i => i.requires_visit), + ) + + return { + items, + taxRate, + addItem, + removeItem, + updateQty, + clearCart, + itemCount, + onetimeTotal, + recurringTotal, + subtotal, + taxAmount, + grandTotal, + requiresVisit, + } +}) diff --git a/apps/client/src/utils/format.js b/apps/client/src/utils/format.js new file mode 100644 index 0000000..08ff866 --- /dev/null +++ b/apps/client/src/utils/format.js @@ -0,0 +1,15 @@ +export const formatPrice = (val) => val.toFixed(2) + ' $' + +// Strip +1/1 prefix from E.164 phone → 10-digit local format for mask +export function normalizePhone (raw) { + if (!raw) return '' + const digits = raw.replace(/\D/g, '') + return digits.length === 11 && digits.startsWith('1') ? digits.slice(1) : digits +} + +// Minimum booking date (J+3) +export function minBookingDate () { + const d = new Date() + d.setDate(d.getDate() + 3) + return d.toISOString().slice(0, 10) +} diff --git a/apps/dispatch/.dockerignore b/apps/dispatch/.dockerignore deleted file mode 100644 index 62b26e1..0000000 --- a/apps/dispatch/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -.git -*.md diff --git a/apps/dispatch/.eslintrc.cjs b/apps/dispatch/.eslintrc.cjs deleted file mode 100644 index e608fd8..0000000 --- a/apps/dispatch/.eslintrc.cjs +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - root: true, - parserOptions: { ecmaVersion: 'latest' }, - env: { browser: true }, - extends: ['plugin:vue/vue3-essential', 'eslint:recommended'], - rules: { - 'no-unused-vars': 'warn', - 'vue/multi-word-component-names': 'off', - }, -} diff --git a/apps/dispatch/.gitignore b/apps/dispatch/.gitignore deleted file mode 100644 index 00735dd..0000000 --- a/apps/dispatch/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -dist/ -.quasar/ -.DS_Store -*.log -npm-debug.log* diff --git a/apps/dispatch/Dockerfile b/apps/dispatch/Dockerfile deleted file mode 100644 index 971e571..0000000 --- a/apps/dispatch/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:20-alpine - -WORKDIR /app - -# Install dependencies first (cached layer) -COPY package*.json ./ -RUN npm install - -# Copy source and build -COPY . . -RUN npm run build - -# The built app lives in /app/dist/pwa/ -# It is extracted by deploy.sh using `docker cp` diff --git a/apps/dispatch/deploy-fast.sh b/apps/dispatch/deploy-fast.sh deleted file mode 100755 index 61e5db3..0000000 --- a/apps/dispatch/deploy-fast.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# ───────────────────────────────────────────────────────────────────────────── -# deploy-fast.sh — Build locally + copy to ERPNext container (no Docker build) -# -# ~5-8s vs ~30s with deploy.sh -# -# Usage: -# chmod +x deploy-fast.sh -# ./deploy-fast.sh -# ───────────────────────────────────────────────────────────────────────────── -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONTAINER="frappe_docker-frontend-1" -DEST="/home/frappe/frappe-bench/sites/assets/dispatch-app" - -cd "$SCRIPT_DIR" - -echo "==> Building PWA locally..." -npx quasar build -m pwa - -echo "==> Deploying to $CONTAINER..." -docker exec "$CONTAINER" mkdir -p "$DEST" -docker cp "$SCRIPT_DIR/dist/pwa/." "$CONTAINER:$DEST/" - -echo "" -echo "Done! (~$(date +%Ss))" -echo " Dispatch : http://localhost:8080/assets/dispatch-app/index.html" -echo " Mobile : http://localhost:8080/assets/dispatch-app/index.html#/mobile" diff --git a/apps/dispatch/deploy.sh b/apps/dispatch/deploy.sh deleted file mode 100755 index 8b82824..0000000 --- a/apps/dispatch/deploy.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# ───────────────────────────────────────────────────────────────────────────── -# deploy.sh — Build the Quasar PWA and deploy to ERPNext Docker -# -# Usage: -# chmod +x deploy.sh -# ./deploy.sh -# -# Accès après déploiement : -# http://localhost:8080/assets/dispatch-app/ -# http://localhost:8080/assets/dispatch-app/#/mobile -# -# To change the target container or path, edit CONTAINER and DEST below. -# ───────────────────────────────────────────────────────────────────────────── -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONTAINER="frappe_docker-frontend-1" -DEST="/home/frappe/frappe-bench/sites/assets/dispatch-app" -IMAGE="dispatch-app-builder" - -echo "==> Building Docker image..." -docker build -t "$IMAGE" "$SCRIPT_DIR" - -echo "==> Extracting build artifacts..." -TMPDIR="$(mktemp -d)" -# Create a temporary container (not running) to copy files out -CID=$(docker create "$IMAGE") -docker cp "$CID:/app/dist/pwa/." "$TMPDIR/" -docker rm "$CID" - -echo "==> Deploying to ERPNext container ($CONTAINER:$DEST)..." -docker exec "$CONTAINER" mkdir -p "$DEST" -docker cp "$TMPDIR/." "$CONTAINER:$DEST/" - -rm -rf "$TMPDIR" - -echo "" -echo "Done!" -echo " Dispatch : http://localhost:8080/assets/dispatch-app/index.html" -echo " Mobile : http://localhost:8080/assets/dispatch-app/index.html#/mobile" diff --git a/apps/dispatch/frappe-setup/add_start_time_field.py b/apps/dispatch/frappe-setup/add_start_time_field.py deleted file mode 100644 index d3967a2..0000000 --- a/apps/dispatch/frappe-setup/add_start_time_field.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Add start_time field to Dispatch Job doctype -""" -import frappe - -def run(): - meta = frappe.get_meta('Dispatch Job') - if meta.has_field('start_time'): - print("✓ Field 'start_time' already exists on Dispatch Job") - return - - doc = frappe.get_doc('DocType', 'Dispatch Job') - doc.append('fields', { - 'fieldname': 'start_time', - 'fieldtype': 'Time', - 'label': 'Heure de début', - 'insert_after': 'scheduled_date', - }) - doc.save(ignore_permissions=True) - frappe.db.commit() - print("✓ Field 'start_time' added to Dispatch Job") - -run() diff --git a/apps/dispatch/frappe-setup/create_dispatch_settings.py b/apps/dispatch/frappe-setup/create_dispatch_settings.py deleted file mode 100644 index 1f2118f..0000000 --- a/apps/dispatch/frappe-setup/create_dispatch_settings.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Dispatch Settings — création du DocType Single dans ERPNext/Frappe -================================================================== -Exécution (depuis le host) : - - docker cp frappe-setup/create_dispatch_settings.py frappe_docker-backend-1:/home/frappe/ - docker exec frappe_docker-backend-1 bash -c \ - "cd /home/frappe/frappe-bench && bench --site $(bench --site-list | head -1) execute /home/frappe/create_dispatch_settings.py" - -Ou directement dans la console bench : - bench --site console - >>> exec(open('/home/frappe/create_dispatch_settings.py').read()) -""" -import frappe - - -FIELDS = [ - # ── ERPNext / Frappe ───────────────────────────────────────────────────── - {'fieldname': 'erp_section', 'fieldtype': 'Section Break', 'label': 'ERPNext / Frappe'}, - {'fieldname': 'erp_url', 'fieldtype': 'Data', 'label': 'URL du serveur', - 'description': 'Ex: http://localhost:8080 ou https://erp.monentreprise.com', - 'default': 'http://localhost:8080'}, - {'fieldname': 'erp_api_key', 'fieldtype': 'Data', 'label': 'API Key', - 'description': 'Profil utilisateur ERPNext → API Access → API Key'}, - {'fieldname': 'erp_api_secret', 'fieldtype': 'Password', 'label': 'API Secret'}, - - # ── Mapbox ─────────────────────────────────────────────────────────────── - {'fieldname': 'mapbox_section', 'fieldtype': 'Section Break', 'label': 'Mapbox'}, - {'fieldname': 'mapbox_token', 'fieldtype': 'Data', 'label': 'Token public (pk_)', - 'description': 'Token public — visible dans le navigateur, limitez le scope dans le dashboard Mapbox', - 'default': 'pk.eyJ1IjoidGFyZ29pbnRlcm5ldCIsImEiOiJjbW13Z3lwMXAwdGt1MnVvamsxNWkybzFkIn0.rdYB17XUdfn96czdnnJ6eg'}, - - # ── Twilio (SMS) ───────────────────────────────────────────────────────── - {'fieldname': 'twilio_section', 'fieldtype': 'Section Break', 'label': 'Twilio — SMS'}, - {'fieldname': 'twilio_account_sid', 'fieldtype': 'Data', 'label': 'Account SID', - 'description': 'Commence par AC — console.twilio.com'}, - {'fieldname': 'twilio_auth_token', 'fieldtype': 'Password', 'label': 'Auth Token'}, - {'fieldname': 'twilio_from_number', 'fieldtype': 'Data', 'label': 'Numéro expéditeur', - 'description': 'Format E.164 : +15141234567'}, - - # ── Stripe ─────────────────────────────────────────────────────────────── - {'fieldname': 'stripe_section', 'fieldtype': 'Section Break', 'label': 'Stripe — Paiements'}, - {'fieldname': 'stripe_mode', 'fieldtype': 'Select', 'label': 'Mode', - 'options': 'test\nlive', 'default': 'test'}, - {'fieldname': 'stripe_publishable_key','fieldtype': 'Data', 'label': 'Clé publique (pk_)'}, - {'fieldname': 'stripe_secret_key', 'fieldtype': 'Password', 'label': 'Clé secrète (sk_)'}, - {'fieldname': 'stripe_webhook_secret', 'fieldtype': 'Password', 'label': 'Webhook Secret (whsec_)'}, - - # ── n8n ────────────────────────────────────────────────────────────────── - {'fieldname': 'n8n_section', 'fieldtype': 'Section Break', 'label': 'n8n — Automatisation'}, - {'fieldname': 'n8n_url', 'fieldtype': 'Data', 'label': 'URL n8n', - 'default': 'http://localhost:5678'}, - {'fieldname': 'n8n_api_key', 'fieldtype': 'Password', 'label': 'API Key n8n'}, - {'fieldname': 'n8n_webhook_base','fieldtype': 'Data', 'label': 'Base URL webhooks', - 'description': 'Ex: http://localhost:5678/webhook — préfixe des webhooks ERPNext → n8n', - 'default': 'http://localhost:5678/webhook'}, - - # ── Templates SMS ──────────────────────────────────────────────────────── - {'fieldname': 'sms_section', 'fieldtype': 'Section Break', 'label': 'Templates SMS'}, - {'fieldname': 'sms_enroute', 'fieldtype': 'Text', 'label': 'Technicien en route', - 'default': 'Bonjour {client_name}, votre technicien {tech_name} est en route et arrivera dans environ {eta} minutes. Réf: {job_id}'}, - {'fieldname': 'sms_completed', 'fieldtype': 'Text', 'label': 'Service complété', - 'default': 'Bonjour {client_name}, votre service ({job_id}) a été complété avec succès. Merci de votre confiance !'}, - {'fieldname': 'sms_assigned', 'fieldtype': 'Text', 'label': 'Job assigné (technicien)', - 'default': 'Nouveau job assigné : {job_id} — {client_name}, {address}. Durée estimée : {duration}h.'}, -] - -PERMISSIONS = [ - {'role': 'System Manager', 'read': 1, 'write': 1, 'create': 1, 'delete': 1}, - {'role': 'Administrator', 'read': 1, 'write': 1, 'create': 1, 'delete': 1}, -] - - -def create_dispatch_settings(): - if frappe.db.exists('DocType', 'Dispatch Settings'): - print("✓ DocType 'Dispatch Settings' existe déjà") - print(" UI : ERPNext Desk → Dispatch Settings") - print(" API : /api/resource/Dispatch Settings/Dispatch Settings") - return - - doc = frappe.new_doc('DocType') - doc.update({ - 'name': 'Dispatch Settings', - 'module': 'Core', - 'custom': 1, - 'is_single': 1, - 'track_changes': 0, - 'fields': FIELDS, - 'permissions': PERMISSIONS, - }) - doc.insert(ignore_permissions=True) - frappe.db.commit() - - print("✓ DocType 'Dispatch Settings' créé avec succès") - print(" UI : ERPNext Desk → Dispatch Settings") - print(" API : /api/resource/Dispatch Settings/Dispatch Settings") - - -create_dispatch_settings() diff --git a/apps/dispatch/index.html b/apps/dispatch/index.html deleted file mode 100644 index d91291f..0000000 --- a/apps/dispatch/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - Dispatch - - - - - - - - - - diff --git a/apps/dispatch/infra/.env.example b/apps/dispatch/infra/.env.example deleted file mode 100644 index 62380a6..0000000 --- a/apps/dispatch/infra/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# ERPNext -ERPNEXT_VERSION=v15.49.2 -DB_ROOT_PASSWORD=admin - -# PostgreSQL (address autocomplete) -PG_DB=dispatch -PG_USER=dispatch -PG_PASSWORD=dispatch diff --git a/apps/dispatch/infra/docker-compose.erpnext.yaml b/apps/dispatch/infra/docker-compose.erpnext.yaml deleted file mode 100644 index 8415afd..0000000 --- a/apps/dispatch/infra/docker-compose.erpnext.yaml +++ /dev/null @@ -1,138 +0,0 @@ -# ERPNext Docker Compose — reference config for rebuilding infrastructure -# Based on frappe_docker: https://github.com/frappe/frappe_docker -# -# Usage: -# cp .env.example .env (edit vars) -# docker compose -f docker-compose.erpnext.yaml up -d -# -# After ERPNext is running, deploy the dispatch PWA: -# cd ../dispatch-app && bash deploy.sh - -x-customizable-image: &customizable_image - image: ${CUSTOM_IMAGE:-frappe/erpnext}:${CUSTOM_TAG:-v15.49.2} - pull_policy: ${PULL_POLICY:-always} - restart: unless-stopped - -x-depends-on-configurator: &depends_on_configurator - depends_on: - configurator: - condition: service_completed_successfully - -x-backend-defaults: &backend_defaults - <<: [*depends_on_configurator, *customizable_image] - volumes: - - sites:/home/frappe/frappe-bench/sites - -services: - configurator: - <<: *backend_defaults - platform: linux/amd64 - entrypoint: ["bash", "-c"] - command: - - > - ls -1 apps > sites/apps.txt; - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: ${DB_HOST:-db} - DB_PORT: ${DB_PORT:-3306} - REDIS_CACHE: ${REDIS_CACHE:-redis-cache:6379} - REDIS_QUEUE: ${REDIS_QUEUE:-redis-queue:6379} - SOCKETIO_PORT: 9000 - depends_on: - db: - condition: service_healthy - redis-cache: - condition: service_started - redis-queue: - condition: service_started - restart: on-failure - - backend: - <<: *backend_defaults - platform: linux/amd64 - - frontend: - <<: *customizable_image - platform: linux/amd64 - command: ["nginx-entrypoint.sh"] - environment: - BACKEND: backend:8000 - SOCKETIO: websocket:9000 - FRAPPE_SITE_NAME_HEADER: $$host - PROXY_READ_TIMEOUT: 120 - CLIENT_MAX_BODY_SIZE: 50m - volumes: - - sites:/home/frappe/frappe-bench/sites - ports: - - "8080:8080" - depends_on: - - backend - - websocket - - websocket: - <<: [*depends_on_configurator, *customizable_image] - platform: linux/amd64 - command: ["node", "/home/frappe/frappe-bench/apps/frappe/socketio.js"] - volumes: - - sites:/home/frappe/frappe-bench/sites - - queue-short: - <<: *backend_defaults - platform: linux/amd64 - command: bench worker --queue short,default - - queue-long: - <<: *backend_defaults - platform: linux/amd64 - command: bench worker --queue long,default,short - - scheduler: - <<: *backend_defaults - platform: linux/amd64 - command: bench schedule - - db: - image: mariadb:10.11 - platform: linux/amd64 - restart: unless-stopped - command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--skip-character-set-client-handshake'] - environment: - MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-admin} - volumes: - - db-data:/var/lib/mysql - healthcheck: - test: mysqladmin ping -h localhost --password=$$MYSQL_ROOT_PASSWORD - interval: 5s - retries: 10 - - redis-cache: - image: redis:7-alpine - restart: unless-stopped - - redis-queue: - image: redis:7-alpine - restart: unless-stopped - - # PostgreSQL for address autocomplete (rqa_addresses table) - postgres: - image: postgres:14-alpine - platform: linux/amd64 - restart: unless-stopped - environment: - POSTGRES_DB: ${PG_DB:-dispatch} - POSTGRES_USER: ${PG_USER:-dispatch} - POSTGRES_PASSWORD: ${PG_PASSWORD:-dispatch} - volumes: - - pg-data:/var/lib/postgresql/data - ports: - - "5432:5432" - -volumes: - sites: - db-data: - pg-data: diff --git a/apps/dispatch/package-lock.json b/apps/dispatch/package-lock.json deleted file mode 100644 index 42efc01..0000000 --- a/apps/dispatch/package-lock.json +++ /dev/null @@ -1,10018 +0,0 @@ -{ - "name": "dispatch-app", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "dispatch-app", - "version": "0.0.1", - "dependencies": { - "@quasar/extras": "^1.16.12", - "html5-qrcode": "^2.3.8", - "pinia": "^2.1.7", - "quasar": "^2.16.10", - "vue": "^3.4.21", - "vue-router": "^4.3.0" - }, - "devDependencies": { - "@quasar/app-vite": "^1.10.0", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.24.0", - "sass": "^1.72.0", - "workbox-build": "7.0.x", - "workbox-cacheable-response": "7.0.x", - "workbox-core": "7.0.x", - "workbox-expiration": "7.0.x", - "workbox-precaching": "7.0.x", - "workbox-routing": "7.0.x", - "workbox-strategies": "7.0.x" - }, - "engines": { - "node": "^18 || ^20", - "npm": ">= 6.13.4" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", - "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", - "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", - "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", - "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", - "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", - "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", - "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", - "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", - "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-replace-supers": "^7.28.6", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", - "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/template": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", - "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", - "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", - "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", - "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", - "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", - "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", - "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", - "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", - "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", - "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", - "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", - "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", - "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", - "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", - "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", - "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", - "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", - "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.28.6", - "@babel/plugin-syntax-import-attributes": "^7.28.6", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.29.0", - "@babel/plugin-transform-async-to-generator": "^7.28.6", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.6", - "@babel/plugin-transform-class-properties": "^7.28.6", - "@babel/plugin-transform-class-static-block": "^7.28.6", - "@babel/plugin-transform-classes": "^7.28.6", - "@babel/plugin-transform-computed-properties": "^7.28.6", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.28.6", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.6", - "@babel/plugin-transform-exponentiation-operator": "^7.28.6", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.28.6", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", - "@babel/plugin-transform-numeric-separator": "^7.28.6", - "@babel/plugin-transform-object-rest-spread": "^7.28.6", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.28.6", - "@babel/plugin-transform-optional-chaining": "^7.28.6", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-private-property-in-object": "^7.28.6", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.29.0", - "@babel/plugin-transform-regexp-modifiers": "^7.28.6", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.28.6", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.28.6", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.15", - "babel-plugin-polyfill-corejs3": "^0.14.0", - "babel-plugin-polyfill-regenerator": "^0.6.6", - "core-js-compat": "^3.48.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", - "cpu": [ - "arm" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@quasar/app-vite": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@quasar/app-vite/-/app-vite-1.11.0.tgz", - "integrity": "sha512-PUeqtYs2liA/O17LJ25jzKckB0MG1ZW/iuDC7NvCMZpYT6Ab66AypiYfPf4WGWeAqricorHVNRyMfMpTscR/hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@quasar/render-ssr-error": "^1.0.3", - "@quasar/vite-plugin": "^1.7.0", - "@rollup/pluginutils": "^4.1.2", - "@types/chrome": "^0.0.208", - "@types/compression": "^1.7.2", - "@types/cordova": "0.0.34", - "@types/express": "^4.17.13", - "@vitejs/plugin-vue": "^2.2.0", - "archiver": "^5.3.0", - "chokidar": "^3.5.3", - "ci-info": "^3.7.1", - "compression": "^1.7.4", - "cross-spawn": "^7.0.3", - "dot-prop": "6.0.1", - "elementtree": "0.1.7", - "esbuild": "0.14.51", - "express": "^4.17.3", - "fast-glob": "3.2.12", - "fs-extra": "^11.1.0", - "html-minifier-terser": "^7.2.0", - "inquirer": "^8.2.1", - "isbinaryfile": "^5.0.0", - "kolorist": "^1.5.1", - "lodash": "^4.17.21", - "minimist": "^1.2.6", - "open": "^8.4.0", - "register-service-worker": "^1.7.2", - "rollup-plugin-visualizer": "^5.5.4", - "sass": "^1.80.2", - "semver": "^7.3.5", - "serialize-javascript": "^6.0.0", - "table": "^6.8.0", - "vite": "^2.9.13", - "webpack-merge": "^5.8.0" - }, - "bin": { - "quasar": "bin/quasar" - }, - "engines": { - "node": "^24 || ^22 || ^20 || ^18 || ^16 || ^14.19", - "npm": ">= 6.14.12", - "yarn": ">= 1.17.3" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - }, - "peerDependencies": { - "@electron/packager": ">= 18", - "electron-builder": ">= 22", - "electron-packager": ">= 15", - "eslint": "^8.11.0", - "pinia": "^2.0.0", - "quasar": "^2.16.0", - "vue": "^3.2.29", - "vue-router": "^4.0.12", - "vuex": "^4.0.0", - "workbox-build": "^6 || 7.0.x" - }, - "peerDependenciesMeta": { - "@electron/packager": { - "optional": true - }, - "electron-builder": { - "optional": true - }, - "electron-packager": { - "optional": true - }, - "eslint": { - "optional": true - }, - "pinia": { - "optional": true - }, - "vuex": { - "optional": true - }, - "workbox-build": { - "optional": true - } - } - }, - "node_modules/@quasar/extras": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.17.0.tgz", - "integrity": "sha512-KqAHdSJfIDauiR1nJ8rqHWT0diqD0QradZKoVIZJAilHAvgwyPIY7MbyR2z4RIMkUIMUSqBZcbshMpEw+9A30w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - } - }, - "node_modules/@quasar/render-ssr-error": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@quasar/render-ssr-error/-/render-ssr-error-1.0.4.tgz", - "integrity": "sha512-pkP0uvhLHu2LWk9NbMwBjs37j1pdImXDz5hS9SX14QSqKZKepsfbre7Z28/GTwL6CrHcr+k9UTYorbpQGTI7EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "stack-trace": "^1.0.0-pre2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - } - }, - "node_modules/@quasar/vite-plugin": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@quasar/vite-plugin/-/vite-plugin-1.11.0.tgz", - "integrity": "sha512-54wCsvuxyMvzrG9t0HauMRL+luA5LEf8yXFPnMkA6q9Hne7k6stw2p8pgFOIPbVFJCuGoYw5VInO5pbpcsI7Ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - }, - "peerDependencies": { - "@vitejs/plugin-vue": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", - "quasar": "^2.16.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.0.0" - } - }, - "node_modules/@rollup/plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", - "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.10.4", - "@rollup/pluginutils": "^3.1.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "@types/babel__core": "^7.1.9", - "rollup": "^1.20.0||^2.0.0" - }, - "peerDependenciesMeta": { - "@types/babel__core": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-babel/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-babel/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", - "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "builtin-modules": "^3.1.0", - "deepmerge": "^4.2.2", - "is-module": "^1.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-replace": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", - "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "magic-string": "^0.25.7" - }, - "peerDependencies": { - "rollup": "^1.20.0 || ^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace/node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-replace/node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/plugin-replace/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ejs": "^3.1.6", - "json5": "^2.2.0", - "magic-string": "^0.25.0", - "string.prototype.matchall": "^4.0.6" - } - }, - "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/chrome": { - "version": "0.0.208", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.208.tgz", - "integrity": "sha512-VDU/JnXkF5qaI7WBz14Azpa2VseZTgML0ia/g/B1sr9OfdOnHiH/zZ7P7qCDqxSlkqJh76/bPc8jLFcx8rHJmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" - } - }, - "node_modules/@types/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cordova": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", - "integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", - "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/filesystem": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", - "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/filewriter": "*" - } - }, - "node_modules/@types/filewriter": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", - "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/har-format": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", - "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz", - "integrity": "sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "vite": "^2.5.10", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz", - "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.30", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", - "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.30", - "@vue/shared": "3.5.30" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", - "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.30", - "@vue/compiler-dom": "3.5.30", - "@vue/compiler-ssr": "3.5.30", - "@vue/shared": "3.5.30", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.8", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", - "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.30", - "@vue/shared": "3.5.30" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/@vue/reactivity": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz", - "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.30" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz", - "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.30", - "@vue/shared": "3.5.30" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", - "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.30", - "@vue/runtime-core": "3.5.30", - "@vue/shared": "3.5.30", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz", - "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.30", - "@vue/shared": "3.5.30" - }, - "peerDependencies": { - "vue": "3.5.30" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz", - "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", - "license": "MIT" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/archiver-utils/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", - "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.322", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.322.tgz", - "integrity": "sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/elementtree": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", - "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "sax": "1.1.4" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.51.tgz", - "integrity": "sha512-+CvnDitD7Q5sT7F+FM65sWkF8wJRf+j9fPcprxYV4j+ohmzVj2W7caUqH2s5kCaCJAfcAICjSlKhDCcvDpU7nw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.51", - "esbuild-android-arm64": "0.14.51", - "esbuild-darwin-64": "0.14.51", - "esbuild-darwin-arm64": "0.14.51", - "esbuild-freebsd-64": "0.14.51", - "esbuild-freebsd-arm64": "0.14.51", - "esbuild-linux-32": "0.14.51", - "esbuild-linux-64": "0.14.51", - "esbuild-linux-arm": "0.14.51", - "esbuild-linux-arm64": "0.14.51", - "esbuild-linux-mips64le": "0.14.51", - "esbuild-linux-ppc64le": "0.14.51", - "esbuild-linux-riscv64": "0.14.51", - "esbuild-linux-s390x": "0.14.51", - "esbuild-netbsd-64": "0.14.51", - "esbuild-openbsd-64": "0.14.51", - "esbuild-sunos-64": "0.14.51", - "esbuild-windows-32": "0.14.51", - "esbuild-windows-64": "0.14.51", - "esbuild-windows-arm64": "0.14.51" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.51.tgz", - "integrity": "sha512-6FOuKTHnC86dtrKDmdSj2CkcKF8PnqkaIXqvgydqfJmqBazCPdw+relrMlhGjkvVdiiGV70rpdnyFmA65ekBCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.51.tgz", - "integrity": "sha512-vBtp//5VVkZWmYYvHsqBRCMMi1MzKuMIn5XDScmnykMTu9+TD9v0NMEDqQxvtFToeYmojdo5UCV2vzMQWJcJ4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.51.tgz", - "integrity": "sha512-YFmXPIOvuagDcwCejMRtCDjgPfnDu+bNeh5FU2Ryi68ADDVlWEpbtpAbrtf/lvFTWPexbgyKgzppNgsmLPr8PA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.51.tgz", - "integrity": "sha512-juYD0QnSKwAMfzwKdIF6YbueXzS6N7y4GXPDeDkApz/1RzlT42mvX9jgNmyOlWKN7YzQAYbcUEJmZJYQGdf2ow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.51.tgz", - "integrity": "sha512-cLEI/aXjb6vo5O2Y8rvVSQ7smgLldwYY5xMxqh/dQGfWO+R1NJOFsiax3IS4Ng300SVp7Gz3czxT6d6qf2cw0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.51.tgz", - "integrity": "sha512-TcWVw/rCL2F+jUgRkgLa3qltd5gzKjIMGhkVybkjk6PJadYInPtgtUBp1/hG+mxyigaT7ib+od1Xb84b+L+1Mg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.51.tgz", - "integrity": "sha512-RFqpyC5ChyWrjx8Xj2K0EC1aN0A37H6OJfmUXIASEqJoHcntuV3j2Efr9RNmUhMfNE6yEj2VpYuDteZLGDMr0w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.51.tgz", - "integrity": "sha512-dxjhrqo5i7Rq6DXwz5v+MEHVs9VNFItJmHBe1CxROWNf4miOGoQhqSG8StStbDkQ1Mtobg6ng+4fwByOhoQoeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.51.tgz", - "integrity": "sha512-LsJynDxYF6Neg7ZC7748yweCDD+N8ByCv22/7IAZglIEniEkqdF4HCaa49JNDLw1UQGlYuhOB8ZT/MmcSWzcWg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.51.tgz", - "integrity": "sha512-D9rFxGutoqQX3xJPxqd6o+kvYKeIbM0ifW2y0bgKk5HPgQQOo2k9/2Vpto3ybGYaFPCE5qTGtqQta9PoP6ZEzw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.51.tgz", - "integrity": "sha512-vS54wQjy4IinLSlb5EIlLoln8buh1yDgliP4CuEHumrPk4PvvP4kTRIG4SzMXm6t19N0rIfT4bNdAxzJLg2k6A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.51.tgz", - "integrity": "sha512-xcdd62Y3VfGoyphNP/aIV9LP+RzFw5M5Z7ja+zdpQHHvokJM7d0rlDRMN+iSSwvUymQkqZO+G/xjb4/75du8BQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.51.tgz", - "integrity": "sha512-syXHGak9wkAnFz0gMmRBoy44JV0rp4kVCEA36P5MCeZcxFq8+fllBC2t6sKI23w3qd8Vwo9pTADCgjTSf3L3rA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.51.tgz", - "integrity": "sha512-kFAJY3dv+Wq8o28K/C7xkZk/X34rgTwhknSsElIqoEo8armCOjMJ6NsMxm48KaWY2h2RUYGtQmr+RGuUPKBhyw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.51.tgz", - "integrity": "sha512-ZZBI7qrR1FevdPBVHz/1GSk1x5GDL/iy42Zy8+neEm/HA7ma+hH/bwPEjeHXKWUDvM36CZpSL/fn1/y9/Hb+1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.51.tgz", - "integrity": "sha512-7R1/p39M+LSVQVgDVlcY1KKm6kFKjERSX1lipMG51NPcspJD1tmiZSmmBXoY5jhHIu6JL1QkFDTx94gMYK6vfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.51.tgz", - "integrity": "sha512-HoHaCswHxLEYN8eBTtyO0bFEWvA3Kdb++hSQ/lLG7TyKF69TeSG0RNoBRAs45x/oCeWaTDntEZlYwAfQlhEtJA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.51.tgz", - "integrity": "sha512-4rtwSAM35A07CBt1/X8RWieDj3ZUHQqUOaEo5ZBs69rt5WAFjP4aqCIobdqOy4FdhYw1yF8Z0xFBTyc9lgPtEg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.51.tgz", - "integrity": "sha512-HoN/5HGRXJpWODprGCgKbdMvrC3A2gqvzewu2eECRw2sYxOUoh2TV1tS+G7bHNapPGI79woQJGV6pFH7GH7qnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.51", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.51.tgz", - "integrity": "sha512-JQDqPjuOH7o+BsKMSddMfmVJXrnYZxXDHsoLHc0xgmAZkOOCflRmC43q31pk79F9xuyWY45jDBPolb5ZgGOf9g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-vue": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", - "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.15", - "semver": "^7.6.3", - "vue-eslint-parser": "^9.4.3", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", - "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, - "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true, - "license": "ISC" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, - "node_modules/html5-qrcode": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.3.8.tgz", - "integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==", - "license": "Apache-2.0" - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", - "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "8.2.7", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", - "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/external-editor": "^1.0.0", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isbinaryfile": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", - "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/lazystream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pinia": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", - "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.3", - "vue-demi": "^0.14.10" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.4.4", - "vue": "^2.7.0 || ^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quasar": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.19.1.tgz", - "integrity": "sha512-VLqIi3H8FWpY3LMJPvZXS7wcgNfKXz/6GZaB2wXH1ggV0HSqo/DNCv1Wcl7oglPxNpc04NAsywfBKpmgWh5R9A==", - "license": "MIT", - "engines": { - "node": ">= 10.18.1", - "npm": ">= 6.13.4", - "yarn": ">= 1.21.1" - }, - "funding": { - "type": "github", - "url": "https://donate.quasar.dev" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/register-service-worker": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/register-service-worker/-/register-service-worker-1.7.2.tgz", - "integrity": "sha512-CiD3ZSanZqcMPRhtfct5K9f7i3OLCcBBWsJjLh1gW9RO/nS94sVzY59iS+fgYBOBqaBpf4EzfqUF3j9IG+xo8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.77.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz", - "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - }, - "peerDependencies": { - "rollup": "^2.0.0" - } - }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/rollup-plugin-visualizer": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz", - "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==", - "dev": true, - "license": "MIT", - "dependencies": { - "open": "^8.4.0", - "picomatch": "^4.0.2", - "source-map": "^0.7.4", - "yargs": "^17.5.1" - }, - "bin": { - "rollup-plugin-visualizer": "dist/bin/cli.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "rolldown": "1.x", - "rollup": "2.x || 3.x || 4.x" - }, - "peerDependenciesMeta": { - "rolldown": { - "optional": true - }, - "rollup": { - "optional": true - } - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sass": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", - "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.1.5", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/sax": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", - "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", - "dev": true, - "license": "ISC" - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/stack-trace": { - "version": "1.0.0-pre2", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", - "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tempy": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", - "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vite": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz", - "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.14.27", - "postcss": "^8.4.13", - "resolve": "^1.22.0", - "rollup": ">=2.59.0 <2.78.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": ">=12.2.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "less": "*", - "sass": "*", - "stylus": "*" - }, - "peerDependenciesMeta": { - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.30", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz", - "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.30", - "@vue/compiler-sfc": "3.5.30", - "@vue/runtime-dom": "3.5.30", - "@vue/server-renderer": "3.5.30", - "@vue/shared": "3.5.30" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vue-router": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", - "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workbox-background-sync": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz", - "integrity": "sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-broadcast-update": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz", - "integrity": "sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-build": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.0.0.tgz", - "integrity": "sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@apideck/better-ajv-errors": "^0.3.1", - "@babel/core": "^7.11.1", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "@rollup/plugin-babel": "^5.2.0", - "@rollup/plugin-node-resolve": "^11.2.1", - "@rollup/plugin-replace": "^2.4.1", - "@surma/rollup-plugin-off-main-thread": "^2.2.3", - "ajv": "^8.6.0", - "common-tags": "^1.8.0", - "fast-json-stable-stringify": "^2.1.0", - "fs-extra": "^9.0.1", - "glob": "^7.1.6", - "lodash": "^4.17.20", - "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", - "rollup-plugin-terser": "^7.0.0", - "source-map": "^0.8.0-beta.0", - "stringify-object": "^3.3.0", - "strip-comments": "^2.0.1", - "tempy": "^0.6.0", - "upath": "^1.2.0", - "workbox-background-sync": "7.0.0", - "workbox-broadcast-update": "7.0.0", - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-google-analytics": "7.0.0", - "workbox-navigation-preload": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-range-requests": "7.0.0", - "workbox-recipes": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0", - "workbox-streams": "7.0.0", - "workbox-sw": "7.0.0", - "workbox-window": "7.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/workbox-build/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/workbox-build/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/workbox-build/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-build/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "deprecated": "The work that was done in this beta branch won't be included in future versions", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workbox-cacheable-response": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz", - "integrity": "sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.0.0.tgz", - "integrity": "sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-expiration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.0.0.tgz", - "integrity": "sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "idb": "^7.0.1", - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-google-analytics": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz", - "integrity": "sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==", - "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-background-sync": "7.0.0", - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "node_modules/workbox-navigation-preload": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz", - "integrity": "sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-precaching": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.0.0.tgz", - "integrity": "sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "node_modules/workbox-range-requests": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz", - "integrity": "sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-recipes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.0.0.tgz", - "integrity": "sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-cacheable-response": "7.0.0", - "workbox-core": "7.0.0", - "workbox-expiration": "7.0.0", - "workbox-precaching": "7.0.0", - "workbox-routing": "7.0.0", - "workbox-strategies": "7.0.0" - } - }, - "node_modules/workbox-routing": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.0.0.tgz", - "integrity": "sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-strategies": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.0.0.tgz", - "integrity": "sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0" - } - }, - "node_modules/workbox-streams": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.0.0.tgz", - "integrity": "sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "workbox-core": "7.0.0", - "workbox-routing": "7.0.0" - } - }, - "node_modules/workbox-sw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.0.0.tgz", - "integrity": "sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA==", - "dev": true, - "license": "MIT" - }, - "node_modules/workbox-window": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.0.0.tgz", - "integrity": "sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/trusted-types": "^2.0.2", - "workbox-core": "7.0.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/zip-stream/node_modules/archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^7.2.3", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - } - } -} diff --git a/apps/dispatch/package.json b/apps/dispatch/package.json deleted file mode 100644 index 5d21fb5..0000000 --- a/apps/dispatch/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "dispatch-app", - "version": "0.0.1", - "description": "Dispatch & Field Service app for ERPNext", - "productName": "Dispatch", - "private": true, - "scripts": { - "dev": "quasar dev", - "build": "quasar build -m pwa", - "lint": "eslint --ext .js,.vue ./src" - }, - "dependencies": { - "@quasar/extras": "^1.16.12", - "html5-qrcode": "^2.3.8", - "pinia": "^2.1.7", - "quasar": "^2.16.10", - "vue": "^3.4.21", - "vue-router": "^4.3.0" - }, - "devDependencies": { - "@quasar/app-vite": "^1.10.0", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.24.0", - "sass": "^1.72.0", - "workbox-build": "7.0.x", - "workbox-cacheable-response": "7.0.x", - "workbox-core": "7.0.x", - "workbox-expiration": "7.0.x", - "workbox-precaching": "7.0.x", - "workbox-routing": "7.0.x", - "workbox-strategies": "7.0.x" - }, - "engines": { - "node": "^18 || ^20", - "npm": ">= 6.13.4" - } -} diff --git a/apps/dispatch/public/icons/apple-icon-120x120.png b/apps/dispatch/public/icons/apple-icon-120x120.png deleted file mode 100644 index 0f15604..0000000 Binary files a/apps/dispatch/public/icons/apple-icon-120x120.png and /dev/null differ diff --git a/apps/dispatch/public/icons/apple-icon-152x152.png b/apps/dispatch/public/icons/apple-icon-152x152.png deleted file mode 100644 index 6d9d360..0000000 Binary files a/apps/dispatch/public/icons/apple-icon-152x152.png and /dev/null differ diff --git a/apps/dispatch/public/icons/apple-icon-167x167.png b/apps/dispatch/public/icons/apple-icon-167x167.png deleted file mode 100644 index 721badb..0000000 Binary files a/apps/dispatch/public/icons/apple-icon-167x167.png and /dev/null differ diff --git a/apps/dispatch/public/icons/apple-icon-180x180.png b/apps/dispatch/public/icons/apple-icon-180x180.png deleted file mode 100644 index 3f94ecf..0000000 Binary files a/apps/dispatch/public/icons/apple-icon-180x180.png and /dev/null differ diff --git a/apps/dispatch/public/icons/icon-128x128.png b/apps/dispatch/public/icons/icon-128x128.png deleted file mode 100644 index 1401176..0000000 Binary files a/apps/dispatch/public/icons/icon-128x128.png and /dev/null differ diff --git a/apps/dispatch/public/icons/icon-192x192.png b/apps/dispatch/public/icons/icon-192x192.png deleted file mode 100644 index 57cbac8..0000000 Binary files a/apps/dispatch/public/icons/icon-192x192.png and /dev/null differ diff --git a/apps/dispatch/public/icons/icon-256x256.png b/apps/dispatch/public/icons/icon-256x256.png deleted file mode 100644 index 12f043e..0000000 Binary files a/apps/dispatch/public/icons/icon-256x256.png and /dev/null differ diff --git a/apps/dispatch/public/icons/icon-384x384.png b/apps/dispatch/public/icons/icon-384x384.png deleted file mode 100644 index df8e257..0000000 Binary files a/apps/dispatch/public/icons/icon-384x384.png and /dev/null differ diff --git a/apps/dispatch/public/icons/icon-512x512.png b/apps/dispatch/public/icons/icon-512x512.png deleted file mode 100644 index 9ca1391..0000000 Binary files a/apps/dispatch/public/icons/icon-512x512.png and /dev/null differ diff --git a/apps/dispatch/public/icons/ms-icon-144x144.png b/apps/dispatch/public/icons/ms-icon-144x144.png deleted file mode 100644 index 83ac328..0000000 Binary files a/apps/dispatch/public/icons/ms-icon-144x144.png and /dev/null differ diff --git a/apps/dispatch/public/icons/safari-pinned-tab.svg b/apps/dispatch/public/icons/safari-pinned-tab.svg deleted file mode 100644 index d6db5ae..0000000 --- a/apps/dispatch/public/icons/safari-pinned-tab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dispatch/quasar.config.js b/apps/dispatch/quasar.config.js deleted file mode 100644 index ab6937b..0000000 --- a/apps/dispatch/quasar.config.js +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-env node */ -const { configure } = require('quasar/wrappers') - -module.exports = configure(function (ctx) { - return { - boot: ['pinia'], - - css: ['app.scss'], - - extras: ['roboto-font', 'material-icons'], - - build: { - target: { - browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'], - node: 'node20', - }, - vueRouterMode: 'hash', - // Base path = where the app is deployed under ERPNext - // Change this if you move the app to a different path - extendViteConf (viteConf) { - viteConf.base = process.env.DEPLOY_BASE || '/assets/dispatch-app/' - }, - }, - - devServer: { - open: false, - // Listen on all interfaces so the container port is reachable from the host - host: '0.0.0.0', - port: 9000, - proxy: { - // Proxy ERPNext API calls to the frontend container - // host.docker.internal resolves to the Docker host on Mac / Windows - '/api': { - target: 'http://host.docker.internal:8080', - changeOrigin: true, - cookieDomainRewrite: 'localhost', - }, - '/assets': { - target: 'http://host.docker.internal:8080', - changeOrigin: true, - }, - }, - }, - - framework: { - config: {}, - // Only load what we actually use — add more as needed - plugins: ['Notify', 'Loading', 'LocalStorage'], - }, - - animations: [], - - pwa: { - workboxMode: 'generateSW', - injectPwaMetaTags: true, - swFilename: 'sw.js', - manifestFilename: 'manifest.json', - useCredentialForManifestTag: false, - workboxOptions: { - skipWaiting: true, - clientsClaim: true, - }, - extendManifestJson (json) { - json.name = 'Dispatch' - json.short_name = 'Dispatch' - json.description = 'Dispatch & Field Service' - json.display = 'standalone' - json.orientation = 'portrait' - json.background_color = '#ffffff' - json.theme_color = '#6366f1' - json.start_url = '.' - }, - }, - } -}) diff --git a/apps/dispatch/scripts/fix_client_script.py b/apps/dispatch/scripts/fix_client_script.py deleted file mode 100644 index 228d402..0000000 --- a/apps/dispatch/scripts/fix_client_script.py +++ /dev/null @@ -1,88 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -SCRIPT = """ -frappe.ui.form.on('Dispatch Job', { - setup(frm) { - frm._addr_bound = false; - }, - refresh(frm) { - if (frm._addr_bound) return; - frappe.run_serially([ - () => frappe.timeout(1), - () => { - try { _bind_address_autocomplete(frm); } - catch(e) { console.warn('Address autocomplete deferred:', e.message); } - } - ]); - } -}); - -function _bind_address_autocomplete(frm) { - var ctrl = frm.fields_dict && frm.fields_dict.address; - if (!ctrl) return; - var input = ctrl.input || (ctrl.$input && ctrl.$input[0]); - if (!input) return; - if (frm._addr_bound) return; - frm._addr_bound = true; - - var dropdown = document.createElement('div'); - dropdown.style.cssText = 'position:absolute;z-index:1000;background:#fff;border:1px solid #d1d5db;border-radius:6px;max-height:250px;overflow-y:auto;width:100%;box-shadow:0 4px 12px rgba(0,0,0,0.15);display:none;'; - input.parentElement.style.position = 'relative'; - input.parentElement.appendChild(dropdown); - - var timer = null; - input.addEventListener('input', function() { - clearTimeout(timer); - var q = this.value.trim(); - if (q.length < 3) { dropdown.style.display = 'none'; return; } - timer = setTimeout(function() { - frappe.call({ - method: 'search_address', - args: { q: q }, - callback: function(r) { - dropdown.innerHTML = ''; - var results = (r && r.results) || (r && r.message && r.message.results) || []; - if (!results.length) { - dropdown.innerHTML = '
Aucun resultat
'; - } else { - results.forEach(function(addr) { - var item = document.createElement('div'); - item.style.cssText = 'padding:8px 12px;cursor:pointer;font-size:13px;border-bottom:1px solid #f3f4f6;'; - var html = '' + addr.address_full + ''; - if (addr.ville) html += ' ' + addr.ville + ''; - item.innerHTML = html; - item.addEventListener('mousedown', function(e) { - e.preventDefault(); - frm.set_value('address', addr.address_full); - if (addr.latitude) frm.set_value('latitude', parseFloat(addr.latitude)); - if (addr.longitude) frm.set_value('longitude', parseFloat(addr.longitude)); - dropdown.style.display = 'none'; - frm.dirty(); - }); - item.addEventListener('mouseenter', function() { this.style.background = '#f3f4f6'; }); - item.addEventListener('mouseleave', function() { this.style.background = ''; }); - dropdown.appendChild(item); - }); - } - dropdown.style.display = 'block'; - } - }); - }, 300); - }); - - input.addEventListener('blur', function() { - setTimeout(function() { dropdown.style.display = 'none'; }, 200); - }); -} -""" - -cs = frappe.get_doc('Client Script', 'Dispatch Job Address Autocomplete') -cs.enabled = 1 -cs.script = SCRIPT -cs.save(ignore_permissions=True) -frappe.db.commit() -print('Client Script fixed and re-enabled') -frappe.destroy() diff --git a/apps/dispatch/scripts/fix_search.py b/apps/dispatch/scripts/fix_search.py deleted file mode 100644 index d93ebc4..0000000 --- a/apps/dispatch/scripts/fix_search.py +++ /dev/null @@ -1,37 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -ss = frappe.get_doc('Server Script', 'Address Autocomplete') -ss.script = """ -query = frappe.form_dict.get("q", "") -if not query or len(query) < 3: - frappe.response["results"] = [] -else: - words = query.strip().split() - conditions = [] - params = {} - for i, w in enumerate(words): - key = "w" + str(i) - conditions.append("f_unaccent(address_full) ILIKE f_unaccent(%({})s)".format(key)) - params[key] = "%" + w + "%" - - where = " AND ".join(conditions) - - results = frappe.db.sql( - "SELECT address_full, ville, code_postal, latitude, longitude " - "FROM rqa_addresses " - "WHERE " + where + " " - "ORDER BY " - "CASE WHEN code_postal LIKE 'J0L%%' THEN 0 WHEN code_postal LIKE 'J0S%%' THEN 1 ELSE 2 END, " - "length(address_full) " - "LIMIT 10", - params, as_dict=True) - - frappe.response["results"] = results -""" -ss.save(ignore_permissions=True) -frappe.db.commit() -print('Fixed: AND-based word search') -frappe.destroy() diff --git a/apps/dispatch/scripts/fix_search2.py b/apps/dispatch/scripts/fix_search2.py deleted file mode 100644 index d028ebb..0000000 --- a/apps/dispatch/scripts/fix_search2.py +++ /dev/null @@ -1,42 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -ss = frappe.get_doc('Server Script', 'Address Autocomplete') -ss.script = """ -query = frappe.form_dict.get("q", "") -if not query or len(query) < 3: - frappe.response["results"] = [] -else: - words = query.strip().split() - conditions = [] - params = {} - for i, w in enumerate(words): - key = "w" + str(i) - params[key] = "%" + w + "%" - conditions.append( - "(f_unaccent(address_full) ILIKE f_unaccent(%({k})s) " - "OR f_unaccent(rue) ILIKE f_unaccent(%({k})s) " - "OR f_unaccent(ville) ILIKE f_unaccent(%({k})s) " - "OR numero ILIKE %({k})s)".format(k=key) - ) - - where = " AND ".join(conditions) - - results = frappe.db.sql( - "SELECT address_full, ville, code_postal, latitude, longitude " - "FROM rqa_addresses " - "WHERE " + where + " " - "ORDER BY " - "CASE WHEN code_postal LIKE 'J0L%%' THEN 0 WHEN code_postal LIKE 'J0S%%' THEN 1 ELSE 2 END, " - "length(address_full) " - "LIMIT 10", - params, as_dict=True) - - frappe.response["results"] = results -""" -ss.save(ignore_permissions=True) -frappe.db.commit() -print('Fixed: search across address_full, rue, ville, numero') -frappe.destroy() diff --git a/apps/dispatch/scripts/fix_search3.py b/apps/dispatch/scripts/fix_search3.py deleted file mode 100644 index 63bfb3b..0000000 --- a/apps/dispatch/scripts/fix_search3.py +++ /dev/null @@ -1,43 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -ss = frappe.get_doc('Server Script', 'Address Autocomplete') -ss.script = """ -query = frappe.form_dict.get("q", "") -if not query or len(query) < 3: - frappe.response["results"] = [] -else: - words = query.strip().split() - conditions = [] - params = {} - idx = 0 - for w in words: - k = "w" + str(idx) - params[k] = "%" + w + "%" - conditions.append( - "(f_unaccent(address_full) ILIKE f_unaccent(%%(%s)s) " - "OR f_unaccent(rue) ILIKE f_unaccent(%%(%s)s) " - "OR f_unaccent(ville) ILIKE f_unaccent(%%(%s)s) " - "OR numero ILIKE %%(%s)s)" % (k, k, k, k) - ) - idx = idx + 1 - - where = " AND ".join(conditions) - - sql = ("SELECT address_full, ville, code_postal, latitude, longitude " - "FROM rqa_addresses " - "WHERE " + where + " " - "ORDER BY " - "CASE WHEN code_postal LIKE 'J0L%%%%' THEN 0 WHEN code_postal LIKE 'J0S%%%%' THEN 1 ELSE 2 END, " - "length(address_full) " - "LIMIT 10") - - results = frappe.db.sql(sql, params, as_dict=True) - frappe.response["results"] = results -""" -ss.save(ignore_permissions=True) -frappe.db.commit() -print('Fixed: no .format(), using % operator') -frappe.destroy() diff --git a/apps/dispatch/scripts/fix_search_abbrev.py b/apps/dispatch/scripts/fix_search_abbrev.py deleted file mode 100644 index 85ef29a..0000000 --- a/apps/dispatch/scripts/fix_search_abbrev.py +++ /dev/null @@ -1,40 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -ss = frappe.get_doc('Server Script', 'Address Autocomplete') -ss.script = """ -query = frappe.form_dict.get("q", "") -if not query or len(query) < 3: - frappe.response["results"] = [] -else: - q = query.strip().lower() - q = q.replace("ste-", "sainte-").replace("ste ", "sainte-") - q = q.replace("st-", "saint-").replace("st ", "saint-") - q = q.replace("boul ", "boulevard ").replace("boul.", "boulevard") - q = q.replace("ave ", "avenue ").replace("ave.", "avenue") - words = q.split() - conditions = [] - for w in words: - escaped = frappe.db.escape("%" + w + "%") - conditions.append("search_text LIKE " + escaped) - - where = " AND ".join(conditions) - - sql = ("SELECT address_full, ville, code_postal, latitude, longitude " - "FROM rqa_addresses " - "WHERE " + where + " " - "ORDER BY " - "CASE WHEN code_postal LIKE 'J0L%' THEN 0 " - "WHEN code_postal LIKE 'J0S%' THEN 1 ELSE 2 END, " - "length(address_full) " - "LIMIT 10") - - results = frappe.db.sql(sql, as_dict=True) - frappe.response["results"] = results -""" -ss.save(ignore_permissions=True) -frappe.db.commit() -print('Updated: query normalizes ste->sainte, st->saint, boul->boulevard') -frappe.destroy() diff --git a/apps/dispatch/scripts/fix_search_fast.py b/apps/dispatch/scripts/fix_search_fast.py deleted file mode 100644 index ab629b4..0000000 --- a/apps/dispatch/scripts/fix_search_fast.py +++ /dev/null @@ -1,35 +0,0 @@ -import frappe, os -os.chdir('/home/frappe/frappe-bench') -frappe.init('frontend', sites_path='/home/frappe/frappe-bench/sites') -frappe.connect() - -ss = frappe.get_doc('Server Script', 'Address Autocomplete') -ss.script = """ -query = frappe.form_dict.get("q", "") -if not query or len(query) < 3: - frappe.response["results"] = [] -else: - words = query.strip().lower().split() - conditions = [] - for w in words: - escaped = frappe.db.escape("%" + w + "%") - conditions.append("search_text LIKE " + escaped) - - where = " AND ".join(conditions) - - sql = ("SELECT address_full, ville, code_postal, latitude, longitude " - "FROM rqa_addresses " - "WHERE " + where + " " - "ORDER BY " - "CASE WHEN code_postal LIKE 'J0L%' THEN 0 " - "WHEN code_postal LIKE 'J0S%' THEN 1 ELSE 2 END, " - "length(address_full) " - "LIMIT 10") - - results = frappe.db.sql(sql, as_dict=True) - frappe.response["results"] = results -""" -ss.save(ignore_permissions=True) -frappe.db.commit() -print('Updated: using frappe.db.escape, no params') -frappe.destroy() diff --git a/apps/dispatch/scripts/import_rqa_addresses.py b/apps/dispatch/scripts/import_rqa_addresses.py deleted file mode 100644 index 467ed7e..0000000 --- a/apps/dispatch/scripts/import_rqa_addresses.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 -""" -Import RQA (Réseau Québécois d'Adresses) CSV into PostgreSQL civic_addresses table. -Handles the ~2.8GB CSV file with streaming/batched inserts. - -Usage: - python3 import_rqa_addresses.py /tmp/RQA_CSV/RQA.csv - -Or from Docker: - docker cp import_rqa_addresses.py frappe_docker-db-1:/tmp/ - docker exec frappe_docker-db-1 python3 /tmp/import_rqa_addresses.py /tmp/RQA.csv -""" - -import csv -import sys -import os -import subprocess -import io - -DB = "_171cf82a99ac0463" -BATCH_SIZE = 10000 - -def get_csv_path(): - if len(sys.argv) > 1: - return sys.argv[1] - # Auto-detect from unzipped location - for p in ['/tmp/RQA_CSV/RQA.csv', '/tmp/RQA.csv', '/tmp/RQA_CSV.csv']: - if os.path.exists(p): - return p - print("Usage: python3 import_rqa_addresses.py ") - sys.exit(1) - -def main(): - csv_path = get_csv_path() - print(f"Reading: {csv_path}") - - # First peek at the header to understand columns - with open(csv_path, 'r', encoding='utf-8-sig', errors='replace') as f: - reader = csv.reader(f, delimiter=',') - header = next(reader) - print(f"Columns ({len(header)}): {header[:15]}...") - - # Show first row - row = next(reader) - print(f"Sample row: {row[:15]}...") - - print(f"\nHeader fields:") - for i, h in enumerate(header): - print(f" {i}: {h}") - -if __name__ == '__main__': - main() diff --git a/apps/dispatch/src-pwa/custom-service-worker.js b/apps/dispatch/src-pwa/custom-service-worker.js deleted file mode 100644 index 16a22ba..0000000 --- a/apps/dispatch/src-pwa/custom-service-worker.js +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-env serviceworker */ - -/* - * This file (which will be your service worker) - * is picked up by the build system ONLY if - * quasar.config.js > pwa > workboxMode is set to "injectManifest" - */ - -import { clientsClaim } from 'workbox-core' -import { precacheAndRoute, cleanupOutdatedCaches, createHandlerBoundToURL } from 'workbox-precaching' -import { registerRoute, NavigationRoute } from 'workbox-routing' - -self.skipWaiting() -clientsClaim() - -// Use with precache injection -precacheAndRoute(self.__WB_MANIFEST) - -cleanupOutdatedCaches() - -// Non-SSR fallback to index.html -// Production SSR fallback to offline.html (except for dev) -if (process.env.MODE !== 'ssr' || process.env.PROD) { - registerRoute( - new NavigationRoute( - createHandlerBoundToURL(process.env.PWA_FALLBACK_HTML), - { denylist: [/sw\.js$/, /workbox-(.)*\.js$/] } - ) - ) -} diff --git a/apps/dispatch/src-pwa/manifest.json b/apps/dispatch/src-pwa/manifest.json deleted file mode 100644 index 8932666..0000000 --- a/apps/dispatch/src-pwa/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "orientation": "portrait", - "background_color": "#ffffff", - "theme_color": "#027be3", - "icons": [ - { - "src": "icons/icon-128x128.png", - "sizes": "128x128", - "type": "image/png" - }, - { - "src": "icons/icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/icon-256x256.png", - "sizes": "256x256", - "type": "image/png" - }, - { - "src": "icons/icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }, - { - "src": "icons/icon-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} diff --git a/apps/dispatch/src-pwa/pwa-flag.d.ts b/apps/dispatch/src-pwa/pwa-flag.d.ts deleted file mode 100644 index cda1c0e..0000000 --- a/apps/dispatch/src-pwa/pwa-flag.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable */ -// THIS FEATURE-FLAG FILE IS AUTOGENERATED, -// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING -import "quasar/dist/types/feature-flag"; - -declare module "quasar/dist/types/feature-flag" { - interface QuasarFeatureFlags { - pwa: true; - } -} diff --git a/apps/dispatch/src-pwa/register-service-worker.js b/apps/dispatch/src-pwa/register-service-worker.js deleted file mode 100644 index c872c56..0000000 --- a/apps/dispatch/src-pwa/register-service-worker.js +++ /dev/null @@ -1,41 +0,0 @@ -import { register } from 'register-service-worker' - -// The ready(), registered(), cached(), updatefound() and updated() -// events passes a ServiceWorkerRegistration instance in their arguments. -// ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration - -register(process.env.SERVICE_WORKER_FILE, { - // The registrationOptions object will be passed as the second argument - // to ServiceWorkerContainer.register() - // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameter - - // registrationOptions: { scope: './' }, - - ready (/* registration */) { - // console.log('Service worker is active.') - }, - - registered (/* registration */) { - // console.log('Service worker has been registered.') - }, - - cached (/* registration */) { - // console.log('Content has been cached for offline use.') - }, - - updatefound (/* registration */) { - // console.log('New content is downloading.') - }, - - updated (/* registration */) { - // console.log('New content is available; please refresh.') - }, - - offline () { - // console.log('No internet connection found. App is running in offline mode.') - }, - - error (/* err */) { - // console.error('Error during service worker registration:', err) - } -}) diff --git a/apps/dispatch/src/App.vue b/apps/dispatch/src/App.vue deleted file mode 100644 index 7471cee..0000000 --- a/apps/dispatch/src/App.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/apps/dispatch/src/api/auth.js b/apps/dispatch/src/api/auth.js deleted file mode 100644 index 97ce031..0000000 --- a/apps/dispatch/src/api/auth.js +++ /dev/null @@ -1,44 +0,0 @@ -// ── ERPNext API auth — service token + Authentik session guard ────────────── -// ERPNext API calls use a service token. User auth is via Authentik forwardAuth -// at the Traefik level. If the Authentik session expires mid-use, API calls -// get redirected (302) — we detect this and reload to trigger re-auth. -// ───────────────────────────────────────────────────────────────────────────── -import { BASE_URL } from 'src/config/erpnext' - -// Service token injected at build time via VITE_ERP_TOKEN env var -// Fallback: read from window.__ERP_TOKEN__ (set by server-side injection) -const SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || window.__ERP_TOKEN__ || '' - -export function authFetch (url, opts = {}) { - opts.headers = { ...opts.headers, Authorization: 'token ' + SERVICE_TOKEN } - opts.redirect = 'manual' // Don't follow redirects — detect Authentik 302 - return fetch(url, opts).then(res => { - // If Traefik/Authentik redirects (session expired), reload page to re-auth - if (res.type === 'opaqueredirect' || res.status === 302 || res.status === 401) { - window.location.reload() - return new Response('{}', { status: 401 }) - } - return res - }) -} - -export function getCSRF () { return null } -export function invalidateCSRF () {} - -export async function login () { window.location.reload() } -export async function logout () { - window.location.href = 'https://auth.targo.ca/if/flow/default-invalidation-flow/' -} - -export async function getLoggedUser () { - try { - const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { - headers: { Authorization: 'token ' + SERVICE_TOKEN }, - }) - if (res.ok) { - const data = await res.json() - return data.message || 'authenticated' - } - } catch {} - return 'authenticated' -} diff --git a/apps/dispatch/src/api/booking.js b/apps/dispatch/src/api/booking.js deleted file mode 100644 index 04580db..0000000 --- a/apps/dispatch/src/api/booking.js +++ /dev/null @@ -1,60 +0,0 @@ -// ── Booking API — crée une demande client dans ERPNext ──────────────────────── -import { BASE_URL } from 'src/config/erpnext' -import { getCSRF } from './auth' - -const SLOT_LABELS = { - matin: 'Matin (8h00 – 12h00)', - aprem: 'Après-midi (12h00 – 17h00)', - soir: 'Soirée (17h00 – 20h00)', -} - -function buildDescription (data) { - const dateLabel = { today: "Aujourd'hui", tomorrow: 'Demain' }[data.date] ?? data.date - return [ - `SERVICE: ${data.service.label}`, - data.serviceNote ? `Détail: ${data.serviceNote}` : null, - `ADRESSE: ${data.address}`, - `DATE: ${dateLabel} — ${SLOT_LABELS[data.slot] ?? data.slot}`, - data.urgent ? '*** URGENT — intervention dans les 2h ***' : null, - '---', - `Client: ${data.contact.name}`, - `Téléphone: ${data.contact.phone}`, - data.contact.email ? `Courriel: ${data.contact.email}` : null, - data.contact.note ? `Note: ${data.contact.note}` : null, - ].filter(Boolean).join('\n') -} - -function localRef () { - return 'DSP-' + Date.now().toString(36).toUpperCase().slice(-6) -} - -export async function createBooking (data) { - const csrf = await getCSRF().catch(() => '') - - // Try ERPNext Lead (CRM module — standard in ERPNext) - try { - const r = await fetch(`${BASE_URL}/api/resource/Lead`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf }, - body: JSON.stringify({ - lead_name: data.contact.name, - mobile_no: data.contact.phone, - email_id: data.contact.email || '', - source: 'Dispatch Booking', - notes: buildDescription(data), - status: 'Open', - lead_owner: '', - }), - }) - const body = await r.json().catch(() => ({})) - if (r.ok && body.data?.name) return body.data.name - } catch (_) { /* fall through */ } - - // Fallback: localStorage + generated ref - const ref = localRef() - const list = JSON.parse(localStorage.getItem('dispatch_bookings') || '[]') - list.push({ ref, ...data, created: new Date().toISOString() }) - localStorage.setItem('dispatch_bookings', JSON.stringify(list)) - return ref -} diff --git a/apps/dispatch/src/api/contractor.js b/apps/dispatch/src/api/contractor.js deleted file mode 100644 index aee16e2..0000000 --- a/apps/dispatch/src/api/contractor.js +++ /dev/null @@ -1,74 +0,0 @@ -// ── Contractor API — inscrit un sous-traitant dans ERPNext ──────────────────── -import { BASE_URL } from 'src/config/erpnext' -import { getCSRF } from './auth' - -function buildNotes (data) { - const services = data.services - .map(s => ` • ${s.label}: ${s.rate}$ / ${s.rateType === 'hourly' ? 'heure' : 'forfait'}`) - .join('\n') - - const days = data.availability.days - .map(d => ({ mon: 'Lun', tue: 'Mar', wed: 'Mer', thu: 'Jeu', fri: 'Ven', sat: 'Sam', sun: 'Dim' }[d])) - .join(', ') - - return [ - `SERVICES OFFERTS:`, - services, - ``, - `ZONE: ${data.availability.city} — rayon ${data.availability.radius}`, - `DISPONIBILITÉ: ${days}`, - data.availability.urgent ? 'Disponible pour urgences' : '', - ``, - data.profile.license ? `Licence/RBQ: ${data.profile.license}` : '', - data.profile.company ? `Entreprise: ${data.profile.company}` : '', - ].filter(s => s !== undefined).join('\n') -} - -function localRef () { - return 'TECH-' + Date.now().toString(36).toUpperCase().slice(-6) -} - -export async function registerContractor (data) { - const csrf = await getCSRF().catch(() => '') - - // Try ERPNext Supplier (standard ERPNext) - try { - const supplierName = data.profile.company - || `${data.profile.firstname} ${data.profile.lastname}` - - const r = await fetch(`${BASE_URL}/api/resource/Supplier`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf }, - body: JSON.stringify({ - supplier_name: supplierName, - supplier_type: 'Individual', - supplier_group: 'Services', - }), - }) - const body = await r.json().catch(() => ({})) - if (r.ok && body.data?.name) { - // Try to create a Contact linked to the supplier - await fetch(`${BASE_URL}/api/resource/Contact`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf }, - body: JSON.stringify({ - first_name: data.profile.firstname, - last_name: data.profile.lastname, - email_ids: [{ email_id: data.profile.email, is_primary: 1 }], - phone_nos: [{ phone: data.profile.phone, is_primary_phone: 1 }], - links: [{ link_doctype: 'Supplier', link_name: body.data.name }], - }), - }).catch(() => {}) - return body.data.name - } - } catch (_) { /* fall through */ } - - // Fallback: localStorage + generated ref - const ref = localRef() - const list = JSON.parse(localStorage.getItem('dispatch_contractors') || '[]') - list.push({ ref, ...data, created: new Date().toISOString(), status: 'pending_review' }) - localStorage.setItem('dispatch_contractors', JSON.stringify(list)) - return ref -} diff --git a/apps/dispatch/src/api/dispatch.js b/apps/dispatch/src/api/dispatch.js deleted file mode 100644 index 5ed4b55..0000000 --- a/apps/dispatch/src/api/dispatch.js +++ /dev/null @@ -1,116 +0,0 @@ -// ── ERPNext Dispatch resource calls ───────────────────────────────────────── -// All ERPNext fetch() calls live here. -// Swap BASE_URL in config/erpnext.js to change the target server. -// ───────────────────────────────────────────────────────────────────────────── -import { BASE_URL } from 'src/config/erpnext' -import { authFetch } from './auth' - -async function apiGet (path) { - const res = await authFetch(BASE_URL + path) - const data = await res.json() - if (data.exc) throw new Error(data.exc) - return data -} - -async function apiPut (doctype, name, body) { - const res = await authFetch( - `${BASE_URL}/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`, - { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }, - ) - if (!res.ok) console.error(`[API] PUT ${doctype}/${name} failed:`, res.status, await res.text().catch(() => '')) - const data = await res.json() - if (data.exc) throw new Error(data.exc) - return data -} - -export async function fetchTechnicians () { - const list = await apiGet('/api/resource/Dispatch%20Technician?fields=["name"]&limit=100') - const names = (list.data || []).map(t => t.name) - if (!names.length) return [] - const docs = await Promise.all( - names.map(n => apiGet(`/api/resource/Dispatch%20Technician/${encodeURIComponent(n)}`).then(d => d.data)) - ) - return docs -} - -// Fetch all jobs with child tables (assistants) -export async function fetchJobs (filters = null) { - // Step 1: get job names from list endpoint - let url = '/api/resource/Dispatch%20Job?fields=["name"]&limit=200' - if (filters) url += '&filters=' + encodeURIComponent(JSON.stringify(filters)) - const list = await apiGet(url) - const names = (list.data || []).map(j => j.name) - if (!names.length) return [] - // Step 2: fetch each doc individually (includes child tables) - const docs = await Promise.all( - names.map(n => apiGet(`/api/resource/Dispatch%20Job/${encodeURIComponent(n)}`).then(d => d.data)) - ) - return docs -} - -export async function updateJob (name, payload) { - return apiPut('Dispatch Job', name, payload) -} - -export async function createJob (payload) { - const res = await authFetch( - `${BASE_URL}/api/resource/Dispatch%20Job`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }, - ) - const data = await res.json() - if (data.exc) throw new Error(data.exc) - return data.data -} - -export async function updateTech (name, payload) { - return apiPut('Dispatch Technician', name, payload) -} - -export async function createTech (payload) { - const res = await authFetch( - `${BASE_URL}/api/resource/Dispatch%20Technician`, - { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }, - ) - const data = await res.json() - if (data.exc) throw new Error(data.exc) - return data.data -} - -export async function deleteTech (name) { - const res = await authFetch( - `${BASE_URL}/api/resource/Dispatch%20Technician/${encodeURIComponent(name)}`, - { method: 'DELETE' }, - ) - if (!res.ok) { - const data = await res.json().catch(() => ({})) - const msg = data._server_messages ? JSON.parse(JSON.parse(data._server_messages)[0]).message : data.exception || 'Delete failed' - throw new Error(msg) - } -} - -export async function fetchTags () { - const data = await apiGet('/api/resource/Dispatch%20Tag?fields=["name","label","color","category"]&limit=200') - return data.data || [] -} - -export async function createTag (label, category = 'Custom', color = '#6b7280') { - const res = await authFetch( - `${BASE_URL}/api/resource/Dispatch%20Tag`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ label, category, color }), - }, - ) - const data = await res.json() - if (data.exc) throw new Error(data.exc) - return data.data -} diff --git a/apps/dispatch/src/api/service-request.js b/apps/dispatch/src/api/service-request.js deleted file mode 100644 index a8946d9..0000000 --- a/apps/dispatch/src/api/service-request.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * API — ServiceRequest, ServiceBid, EquipmentInstall - * - * Tries Frappe custom doctypes first, falls back to Lead + localStorage - * so the app works before the backend doctypes are created. - */ - -const BASE = '' - -async function getCSRF () { - const m = document.cookie.match(/csrftoken=([^;]+)/) - if (m) return m[1] - const r = await fetch('/api/method/frappe.auth.get_csrf_token', { credentials: 'include' }) - const d = await r.json().catch(() => ({})) - return d.csrf_token || '' -} - -async function frappePOST (doctype, data) { - const csrf = await getCSRF().catch(() => '') - const r = await fetch(`${BASE}/api/resource/${encodeURIComponent(doctype)}`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf }, - body: JSON.stringify(data), - }) - if (!r.ok) throw new Error(`HTTP ${r.status}`) - const body = await r.json() - return body.data -} - -async function frappePUT (doctype, name, data) { - const csrf = await getCSRF().catch(() => '') - const r = await fetch(`${BASE}/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(name)}`, { - method: 'PUT', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf }, - body: JSON.stringify(data), - }) - if (!r.ok) throw new Error(`HTTP ${r.status}`) - const body = await r.json() - return body.data -} - -async function frappeGET (doctype, filters = {}, fields = ['name']) { - const params = new URLSearchParams({ - fields: JSON.stringify(fields), - filters: JSON.stringify(filters), - limit: 50, - }) - const r = await fetch(`${BASE}/api/resource/${encodeURIComponent(doctype)}?${params}`, { - credentials: 'include', - }) - if (!r.ok) throw new Error(`HTTP ${r.status}`) - const body = await r.json() - return body.data || [] -} - -// ───────────────────────────────────────────────────────────────────────────── -// ServiceRequest -// ───────────────────────────────────────────────────────────────────────────── - -export async function createServiceRequest (data) { - /** - * data = { - * service_type: 'internet' | 'tv' | 'telephone' | 'multi', - * problem_type: string, - * description: string, - * address: string, - * coordinates: [lng, lat], - * preferred_dates: [{ date, time_slot, time_slots[] }, ...], // up to 3 - * contact: { name, phone, email }, - * urgency: 'normal' | 'urgent', - * budget: { id, label, min, max } | null, - * } - */ - const ref = 'SR-' + Date.now().toString(36).toUpperCase().slice(-6) - - // Try Frappe ServiceRequest doctype - try { - const doc = await frappePOST('Service Request', { - customer_name: data.contact.name, - phone: data.contact.phone, - email: data.contact.email, - service_type: data.service_type, - problem_type: data.problem_type, - description: data.description, - address: data.address, - lng: data.coordinates?.[0] || 0, - lat: data.coordinates?.[1] || 0, - preferred_date_1: data.preferred_dates[0]?.date || '', - time_slot_1: data.preferred_dates[0]?.time_slot || '', - preferred_date_2: data.preferred_dates[1]?.date || '', - time_slot_2: data.preferred_dates[1]?.time_slot || '', - preferred_date_3: data.preferred_dates[2]?.date || '', - time_slot_3: data.preferred_dates[2]?.time_slot || '', - urgency: data.urgency || 'normal', - budget_label: data.budget?.label || '', - budget_min: data.budget?.min || 0, - budget_max: data.budget?.max || 0, - status: 'New', - }) - return { ref: doc.name, source: 'frappe' } - } catch (_) {} - - // Fallback: create as Frappe Lead + HD Ticket - try { - const notes = buildNotes(data) - const doc = await frappePOST('Lead', { - lead_name: data.contact.name, - mobile_no: data.contact.phone, - email_id: data.contact.email || '', - source: 'Dispatch Booking', - lead_owner: '', - status: 'Open', - notes, - }) - return { ref: doc.name, source: 'lead' } - } catch (_) {} - - // Final fallback: localStorage - const list = JSON.parse(localStorage.getItem('dispatch_service_requests') || '[]') - list.push({ ref, ...data, lng: data.coordinates?.[0] || 0, lat: data.coordinates?.[1] || 0, budget_label: data.budget?.label || '', created: new Date().toISOString(), status: 'new' }) - localStorage.setItem('dispatch_service_requests', JSON.stringify(list)) - return { ref, source: 'local' } -} - -function buildNotes (data) { - const dates = data.preferred_dates - .filter(d => d.date) - .map((d, i) => ` Date ${i + 1}: ${d.date} — ${d.time_slot}`) - .join('\n') - return [ - `SERVICE: ${data.service_type?.toUpperCase()}`, - `PROBLÈME: ${data.problem_type}`, - `DESCRIPTION: ${data.description}`, - `ADRESSE: ${data.address}`, - `URGENCE: ${data.urgency}`, - '', - 'DATES PRÉFÉRÉES:', - dates, - ].join('\n') -} - -export async function fetchServiceRequests (status = null) { - try { - const filters = status ? { status } : {} - return await frappeGET('Service Request', filters, [ - 'name', 'customer_name', 'phone', 'service_type', 'problem_type', - 'description', 'address', 'status', 'urgency', - 'preferred_date_1', 'time_slot_1', - 'preferred_date_2', 'time_slot_2', - 'preferred_date_3', 'time_slot_3', - 'confirmed_date', 'creation', - 'budget_label', 'budget_min', 'budget_max', - ]) - } catch (_) { - return JSON.parse(localStorage.getItem('dispatch_service_requests') || '[]') - } -} - -export async function updateServiceRequestStatus (name, status, confirmedDate = null) { - try { - const data = {} - if (status) data.status = status - if (confirmedDate) data.confirmed_date = confirmedDate - if (Object.keys(data).length === 0) return - return await frappePUT('Service Request', name, data) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_service_requests') || '[]') - const item = list.find(r => r.ref === name || r.name === name) - if (item) { - if (status) item.status = status - if (confirmedDate) item.confirmed_date = confirmedDate - } - localStorage.setItem('dispatch_service_requests', JSON.stringify(list)) - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// ServiceBid (tech bids on a date) -// ───────────────────────────────────────────────────────────────────────────── - -export async function createServiceBid (data) { - /** - * data = { request, technician, proposed_date, time_slot, estimated_duration, notes, price } - */ - try { - return await frappePOST('Service Bid', { ...data, status: 'Pending' }) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_service_bids') || '[]') - const bid = { ref: 'BID-' + Date.now().toString(36).toUpperCase().slice(-6), ...data, status: 'pending', created: new Date().toISOString() } - list.push(bid) - localStorage.setItem('dispatch_service_bids', JSON.stringify(list)) - return bid - } -} - -export async function fetchBidsForRequest (requestName) { - try { - return await frappeGET('Service Bid', { request: requestName }, [ - 'name', 'technician', 'proposed_date', 'time_slot', - 'estimated_duration', 'notes', 'status', 'creation', - ]) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_service_bids') || '[]') - return list.filter(b => b.request === requestName) - } -} - -export async function fetchBidsForTech (techName) { - try { - return await frappeGET('Service Bid', { technician: techName }, [ - 'name', 'request', 'proposed_date', 'time_slot', 'status', 'creation', - ]) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_service_bids') || '[]') - return list.filter(b => b.technician === techName) - } -} - -export async function fetchOpenRequests () { - try { - return await frappeGET('Service Request', { status: ['in', ['New', 'Bidding']] }, [ - 'name', 'customer_name', 'service_type', 'problem_type', 'description', - 'address', 'lng', 'lat', 'urgency', 'preferred_date_1', 'time_slot_1', - 'preferred_date_2', 'time_slot_2', 'preferred_date_3', 'time_slot_3', - 'creation', 'budget_label', 'budget_min', 'budget_max', - ]) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_service_requests') || '[]') - return list.filter(r => ['new', 'bidding'].includes(r.status)) - } -} - -export async function acceptBid (bidName, requestName, confirmedDate) { - try { - await frappePUT('Service Bid', bidName, { status: 'Accepted' }) - await frappePUT('Service Request', requestName, { status: 'Confirmed', confirmed_date: confirmedDate }) - } catch (_) {} -} - -// ───────────────────────────────────────────────────────────────────────────── -// EquipmentInstall (barcode scan on site) -// ───────────────────────────────────────────────────────────────────────────── - -export async function createEquipmentInstall (data) { - /** - * data = { request, barcode, equipment_type, brand, model, notes, photo_base64 } - */ - try { - return await frappePOST('Equipment Install', { - ...data, - installation_date: new Date().toISOString().split('T')[0], - }) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_equipment') || '[]') - const item = { ref: 'EQ-' + Date.now().toString(36).toUpperCase().slice(-6), ...data, created: new Date().toISOString() } - list.push(item) - localStorage.setItem('dispatch_equipment', JSON.stringify(list)) - return item - } -} - -export async function fetchEquipmentForRequest (requestName) { - try { - return await frappeGET('Equipment Install', { request: requestName }, [ - 'name', 'barcode', 'equipment_type', 'brand', 'model', 'notes', 'installation_date', - ]) - } catch (_) { - const list = JSON.parse(localStorage.getItem('dispatch_equipment') || '[]') - return list.filter(e => e.request === requestName) - } -} diff --git a/apps/dispatch/src/api/settings.js b/apps/dispatch/src/api/settings.js deleted file mode 100644 index 43060a0..0000000 --- a/apps/dispatch/src/api/settings.js +++ /dev/null @@ -1,97 +0,0 @@ -// ── Dispatch Settings — lecture/écriture du DocType Single ERPNext ─────────── -import { BASE_URL } from 'src/config/erpnext' -import { getCSRF } from './auth' - -const DOCTYPE = 'Dispatch Settings' -const NAME = 'Dispatch Settings' - -function isDocTypeError (body) { - const s = JSON.stringify(body) - return s.includes('dispatch_settings') || s.includes('DoesNotExist') || s.includes('No module named') -} - -export async function fetchSettings () { - const r = await fetch(`${BASE_URL}/api/resource/${DOCTYPE}/${NAME}`, { - credentials: 'include', - }) - if (!r.ok) { - const body = await r.json().catch(() => ({})) - if (r.status === 404 || isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') - throw new Error(`Erreur HTTP ${r.status}`) - } - const body = await r.json() - // Frappe peut retourner 200 avec une exception dans le corps - if (body.exc_type || body.exception) { - if (isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') - throw new Error(body.exc_type || 'Erreur Frappe') - } - return body.data -} - -export async function saveSettings (payload) { - const csrf = await getCSRF() - const r = await fetch(`${BASE_URL}/api/resource/${DOCTYPE}/${NAME}`, { - method: 'PUT', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf || '' }, - body: JSON.stringify(payload), - }) - const body = await r.json().catch(() => ({})) - if (!r.ok || body.exc_type || body.exception) { - if (isDocTypeError(body)) throw new Error('DOCTYPE_NOT_FOUND') - throw new Error(body._error_message || body.exc_type || `Erreur HTTP ${r.status}`) - } - return body -} - -// ── Création du DocType via API (bouton Initialiser dans l'admin) ───────────── -const DOCTYPE_FIELDS = [ - { fieldname: 'erp_section', fieldtype: 'Section Break', label: 'ERPNext / Frappe' }, - { fieldname: 'erp_url', fieldtype: 'Data', label: 'URL du serveur', default: 'http://localhost:8080' }, - { fieldname: 'erp_api_key', fieldtype: 'Data', label: 'API Key' }, - { fieldname: 'erp_api_secret', fieldtype: 'Password', label: 'API Secret' }, - { fieldname: 'mapbox_section', fieldtype: 'Section Break', label: 'Mapbox' }, - { fieldname: 'mapbox_token', fieldtype: 'Data', label: 'Token public (pk_)' }, - { fieldname: 'twilio_section', fieldtype: 'Section Break', label: 'Twilio — SMS' }, - { fieldname: 'twilio_account_sid', fieldtype: 'Data', label: 'Account SID' }, - { fieldname: 'twilio_auth_token', fieldtype: 'Password', label: 'Auth Token' }, - { fieldname: 'twilio_from_number', fieldtype: 'Data', label: 'Numéro expéditeur' }, - { fieldname: 'stripe_section', fieldtype: 'Section Break', label: 'Stripe — Paiements' }, - { fieldname: 'stripe_mode', fieldtype: 'Select', label: 'Mode', options: 'test\nlive', default: 'test' }, - { fieldname: 'stripe_publishable_key', fieldtype: 'Data', label: 'Clé publique (pk_)' }, - { fieldname: 'stripe_secret_key', fieldtype: 'Password', label: 'Clé secrète (sk_)' }, - { fieldname: 'stripe_webhook_secret',fieldtype: 'Password', label: 'Webhook Secret (whsec_)' }, - { fieldname: 'n8n_section', fieldtype: 'Section Break', label: 'n8n — Automatisation' }, - { fieldname: 'n8n_url', fieldtype: 'Data', label: 'URL n8n', default: 'http://localhost:5678' }, - { fieldname: 'n8n_api_key', fieldtype: 'Password', label: 'API Key n8n' }, - { fieldname: 'n8n_webhook_base', fieldtype: 'Data', label: 'Base URL webhooks', default: 'http://localhost:5678/webhook' }, - { fieldname: 'sms_section', fieldtype: 'Section Break', label: 'Templates SMS' }, - { fieldname: 'sms_enroute', fieldtype: 'Text', label: 'Technicien en route', - default: 'Bonjour {client_name}, votre technicien {tech_name} est en route et arrivera dans environ {eta} minutes. Réf: {job_id}' }, - { fieldname: 'sms_completed', fieldtype: 'Text', label: 'Service complété', - default: 'Bonjour {client_name}, votre service ({job_id}) a été complété avec succès. Merci de votre confiance !' }, - { fieldname: 'sms_assigned', fieldtype: 'Text', label: 'Job assigné (technicien)', - default: 'Nouveau job assigné : {job_id} — {client_name}, {address}. Durée estimée : {duration}h.' }, -] - -export async function createDocType () { - const csrf = await getCSRF() - const r = await fetch(`${BASE_URL}/api/resource/DocType`, { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/json', 'X-Frappe-CSRF-Token': csrf || '' }, - body: JSON.stringify({ - name: DOCTYPE, module: 'Core', custom: 1, is_single: 1, track_changes: 0, - fields: DOCTYPE_FIELDS, - permissions: [ - { role: 'System Manager', read: 1, write: 1, create: 1 }, - { role: 'Administrator', read: 1, write: 1, create: 1 }, - ], - }), - }) - const body = await r.json().catch(() => ({})) - if (!r.ok || body.exc_type) { - throw new Error(body._error_message || body.exc_type || `Erreur HTTP ${r.status}`) - } - return body -} diff --git a/apps/dispatch/src/api/traccar.js b/apps/dispatch/src/api/traccar.js deleted file mode 100644 index a3b6017..0000000 --- a/apps/dispatch/src/api/traccar.js +++ /dev/null @@ -1,94 +0,0 @@ -// ── Traccar GPS API ────────────────────────────────────────────────────────── -// Polls Traccar for real-time device positions. -// Auth: session cookie via POST /api/session -// ───────────────────────────────────────────────────────────────────────────── - -// Use proxy on same origin to avoid mixed content (HTTPS → HTTP) -const TRACCAR_URL = window.location.hostname === 'localhost' - ? 'http://tracker.targointernet.com:8082' - : window.location.origin + '/traccar' -const TRACCAR_USER = 'louis@targo.ca' -const TRACCAR_PASS = 'targo2026' - -let _devices = [] - -// Use Basic auth — works through proxy without cookies -function authOpts () { - return { - headers: { - Authorization: 'Basic ' + btoa(TRACCAR_USER + ':' + TRACCAR_PASS), - Accept: 'application/json', - } - } -} - -// ── Devices ────────────────────────────────────────────────────────────────── -export async function fetchDevices () { - try { - const res = await fetch(TRACCAR_URL + '/api/devices?all=true', authOpts()) - if (res.ok) { - _devices = await res.json() - return _devices - } - } catch {} - return _devices -} - -// ── Positions ──────────────────────────────────────────────────────────────── -// Traccar API only supports ONE deviceId per request — fetch in parallel -export async function fetchPositions (deviceIds = null) { - if (!deviceIds || !deviceIds.length) return [] - const results = await Promise.allSettled( - deviceIds.map(id => - fetch(TRACCAR_URL + '/api/positions?deviceId=' + id, authOpts()) - .then(r => r.ok ? r.json() : []) - ) - ) - return results.flatMap(r => r.status === 'fulfilled' ? r.value : []) -} - -// ── Get position for a specific device ─────────────────────────────────────── -export async function fetchDevicePosition (deviceId) { - const positions = await fetchPositions([deviceId]) - return positions[0] || null -} - -// ── Get all positions mapped by deviceId ───────────────────────────────────── -export async function fetchAllPositions () { - // Get devices we care about (online + offline with recent position) - if (!_devices.length) await fetchDevices() - const deviceIds = _devices.filter(d => d.positionId).map(d => d.id) - if (!deviceIds.length) return {} - - const positions = await fetchPositions(deviceIds) - const map = {} - positions.forEach(p => { map[p.deviceId] = p }) - return map -} - -// ── Utility: match device to tech by uniqueId or name ──────────────────────── -export function matchDeviceToTech (devices, techs) { - const matched = [] - for (const tech of techs) { - const traccarId = tech.traccarDeviceId - if (!traccarId) continue - const device = devices.find(d => d.id === parseInt(traccarId) || d.uniqueId === traccarId) - if (device) matched.push({ tech, device }) - } - return matched -} - -// ── Session (required for WebSocket auth) ──────────────────────────────────── -export async function createTraccarSession () { - try { - const res = await fetch(TRACCAR_URL + '/api/session', { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ email: TRACCAR_USER, password: TRACCAR_PASS }), - }) - return res.ok - } catch { return false } -} - -export { TRACCAR_URL, _devices as cachedDevices } diff --git a/apps/dispatch/src/boot/pinia.js b/apps/dispatch/src/boot/pinia.js deleted file mode 100644 index aedcfbf..0000000 --- a/apps/dispatch/src/boot/pinia.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createPinia } from 'pinia' - -export default ({ app }) => { - app.use(createPinia()) -} diff --git a/apps/dispatch/src/components/TagInput.vue b/apps/dispatch/src/components/TagInput.vue deleted file mode 100644 index e16cb36..0000000 --- a/apps/dispatch/src/components/TagInput.vue +++ /dev/null @@ -1,137 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/composables/useAutoDispatch.js b/apps/dispatch/src/composables/useAutoDispatch.js deleted file mode 100644 index 214d3a4..0000000 --- a/apps/dispatch/src/composables/useAutoDispatch.js +++ /dev/null @@ -1,140 +0,0 @@ -// ── Auto-dispatch composable: autoDistribute + optimizeRoute ───────────────── -import { localDateStr } from './useHelpers' -import { updateJob } from 'src/api/dispatch' - -export function useAutoDispatch (deps) { - const { store, MAPBOX_TOKEN, filteredResources, periodStart, getJobDate, unscheduledJobs, bottomSelected, dispatchCriteria, pushUndo, invalidateRoutes } = deps - - async function autoDistribute () { - const techs = filteredResources.value - if (!techs.length) return - const today = localDateStr(new Date()) - let pool - if (bottomSelected.value.size) { - pool = [...bottomSelected.value].map(id => store.jobs.find(j => j.id === id)).filter(Boolean) - } else { - pool = unscheduledJobs.value.filter(j => !j.scheduledDate || j.scheduledDate === today) - } - if (!pool.length) return - // Jobs with coords get proximity-based assignment, jobs without get load-balanced only - const withCoords = pool.filter(j => j.coords && (j.coords[0] !== 0 || j.coords[1] !== 0)) - const noCoords = pool.filter(j => !j.coords || (j.coords[0] === 0 && j.coords[1] === 0)) - const unassigned = [...withCoords, ...noCoords] - if (!unassigned.length) return - - const prevQueues = {} - techs.forEach(t => { prevQueues[t.id] = [...t.queue] }) - const prevAssignments = unassigned.map(j => ({ jobId: j.id, techId: j.assignedTech, scheduledDate: j.scheduledDate })) - - function techLoadForDay (tech, dayStr) { - return tech.queue.filter(j => getJobDate(j.id) === dayStr).reduce((s, j) => s + (parseFloat(j.duration) || 1), 0) - } - function dist (a, b) { - if (!a || !b) return 999 - const dx = (a[0] - b[0]) * 80, dy = (a[1] - b[1]) * 111 - return Math.sqrt(dx * dx + dy * dy) - } - function techLastPosForDay (tech, dayStr) { - const dj = tech.queue.filter(j => getJobDate(j.id) === dayStr) - if (dj.length) { const last = dj[dj.length - 1]; if (last.coords && last.coords[0] !== 0) return last.coords } - return tech.coords - } - - const criteria = dispatchCriteria.value.filter(c => c.enabled) - const sorted = [...unassigned].sort((a, b) => { - for (const c of criteria) { - if (c.id === 'urgency') { - const p = { high: 0, medium: 1, low: 2 } - const diff = (p[a.priority] ?? 2) - (p[b.priority] ?? 2) - if (diff !== 0) return diff - } - } - return 0 - }) - - const useSkills = criteria.some(c => c.id === 'skills') - const weights = {} - criteria.forEach((c, i) => { weights[c.id] = criteria.length - i }) - - sorted.forEach(job => { - const assignDay = job.scheduledDate || today - let bestTech = null, bestScore = Infinity - techs.forEach(tech => { - let score = 0 - if (weights.balance) score += techLoadForDay(tech, assignDay) * (weights.balance || 1) - if (weights.proximity && job.coords && (job.coords[0] !== 0 || job.coords[1] !== 0)) score += dist(techLastPosForDay(tech, assignDay), job.coords) / 60 * (weights.proximity || 1) - if (weights.skills && useSkills) { - const jt = job.tags || [], tt = tech.tags || [] - score += (jt.length > 0 ? (jt.length - jt.filter(t => tt.includes(t)).length) * 2 : 0) * (weights.skills || 1) - } - if (score < bestScore) { bestScore = score; bestTech = tech } - }) - if (bestTech) store.smartAssign(job.id, bestTech.id, assignDay) - }) - - pushUndo({ type: 'autoDistribute', assignments: prevAssignments, prevQueues }) - bottomSelected.value = new Set() - invalidateRoutes() - } - - async function optimizeRoute (tech) { - const dayStr = localDateStr(periodStart.value) - const dayJobs = tech.queue.filter(j => getJobDate(j.id) === dayStr) - if (dayJobs.length < 2) return - const jobsWithCoords = dayJobs.filter(j => j.coords && (j.coords[0] !== 0 || j.coords[1] !== 0)) - if (jobsWithCoords.length < 2) return - - const urgent = jobsWithCoords.filter(j => j.priority === 'high') - const normal = jobsWithCoords.filter(j => j.priority !== 'high') - - function nearestNeighbor (start, jobs) { - const result = [], remaining = [...jobs] - let cur = start - while (remaining.length) { - let bi = 0, bd = Infinity - remaining.forEach((j, i) => { - const dx = j.coords[0] - cur[0], dy = j.coords[1] - cur[1], d = dx * dx + dy * dy - if (d < bd) { bd = d; bi = i } - }) - result.push(remaining.splice(bi, 1)[0]) - cur = result.at(-1).coords - } - return result - } - - const home = (tech.coords?.[0] && tech.coords?.[1]) ? tech.coords : jobsWithCoords[0].coords - const orderedUrgent = nearestNeighbor(home, urgent) - const orderedNormal = nearestNeighbor(orderedUrgent.length ? orderedUrgent.at(-1).coords : home, normal) - const reordered = [...orderedUrgent, ...orderedNormal] - - try { - const hasHome = !!(tech.coords?.[0] && tech.coords?.[1]) - const coords = [] - if (hasHome) coords.push(`${tech.coords[0]},${tech.coords[1]}`) - reordered.forEach(j => coords.push(`${j.coords[0]},${j.coords[1]}`)) - if (coords.length <= 12) { - const url = `https://api.mapbox.com/optimized-trips/v1/mapbox/driving/${coords.join(';')}?overview=false${hasHome ? '&source=first' : ''}&roundtrip=false&destination=any&access_token=${MAPBOX_TOKEN}` - const res = await fetch(url) - const data = await res.json() - if (data.code === 'Ok' && data.waypoints) { - const off = hasHome ? 1 : 0, uc = orderedUrgent.length - const mu = reordered.slice(0, uc).map((j, i) => ({ job: j, o: data.waypoints[i + off].waypoint_index })).sort((a, b) => a.o - b.o).map(x => x.job) - const mn = reordered.slice(uc).map((j, i) => ({ job: j, o: data.waypoints[i + uc + off].waypoint_index })).sort((a, b) => a.o - b.o).map(x => x.job) - reordered.length = 0 - reordered.push(...mu, ...mn) - } - } - } catch (_) {} - - pushUndo({ type: 'optimizeRoute', techId: tech.id, prevQueue: [...tech.queue] }) - const otherJobs = tech.queue.filter(j => getJobDate(j.id) !== dayStr) - tech.queue = [...reordered, ...otherJobs] - tech.queue.forEach((j, i) => { - j.routeOrder = i - updateJob(j.name || j.id, { route_order: i, start_time: '' }).catch(() => {}) - }) - invalidateRoutes() - } - - return { autoDistribute, optimizeRoute } -} diff --git a/apps/dispatch/src/composables/useBottomPanel.js b/apps/dispatch/src/composables/useBottomPanel.js deleted file mode 100644 index 19ecdcd..0000000 --- a/apps/dispatch/src/composables/useBottomPanel.js +++ /dev/null @@ -1,120 +0,0 @@ -// ── Bottom panel composable: unassigned jobs table, multi-select, criteria ──── -import { ref, computed, watch } from 'vue' -import { localDateStr } from './useHelpers' - -export function useBottomPanel (store, todayStr, unscheduledJobs, { pushUndo, smartAssign, invalidateRoutes, periodStart }) { - const bottomPanelOpen = ref(localStorage.getItem('sbv2-bottomPanel') !== 'false') - const bottomPanelH = ref(parseInt(localStorage.getItem('sbv2-bottomH')) || 220) - watch(bottomPanelOpen, v => localStorage.setItem('sbv2-bottomPanel', v ? 'true' : 'false')) - - // ── Grouped by date ────────────────────────────────────────────────────────── - const unassignedGrouped = computed(() => { - const today = todayStr - const jobs = unscheduledJobs.value.slice() - jobs.sort((a, b) => { - const da = a.scheduledDate || '9999-99-99' - const db = b.scheduledDate || '9999-99-99' - const aToday = da === today ? 0 : 1 - const bToday = db === today ? 0 : 1 - if (aToday !== bToday) return aToday - bToday - if (da !== db) return da.localeCompare(db) - const prio = { high: 0, medium: 1, low: 2 } - return (prio[a.priority] ?? 2) - (prio[b.priority] ?? 2) - }) - const groups = [] - let currentDate = null - jobs.forEach(job => { - const d = job.scheduledDate || null - if (d !== currentDate) { - currentDate = d - let label = d === today ? "Aujourd'hui" : d ? d : 'Sans date' - if (d && d !== today) { - const dt = new Date(d + 'T00:00:00') - label = dt.toLocaleDateString('fr-CA', { weekday: 'short', day: 'numeric', month: 'short' }) - } - groups.push({ date: d, label, jobs: [] }) - } - groups.at(-1).jobs.push(job) - }) - return groups - }) - - // ── Resize ─────────────────────────────────────────────────────────────────── - function startBottomResize (e) { - e.preventDefault() - const startY = e.clientY, startH = bottomPanelH.value - function onMove (ev) { bottomPanelH.value = Math.max(100, Math.min(window.innerHeight * 0.6, startH - (ev.clientY - startY))) } - function onUp () { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); localStorage.setItem('sbv2-bottomH', String(bottomPanelH.value)) } - document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp) - } - - // ── Multi-select ───────────────────────────────────────────────────────────── - const bottomSelected = ref(new Set()) - function toggleBottomSelect (jobId, event) { - const s = new Set(bottomSelected.value) - // Checkbox click: always toggle (no modifier needed) - // Shift+click: range select - if (event?.shiftKey && s.size) { - const flat = unassignedGrouped.value.flatMap(g => g.jobs) - const ids = flat.map(j => j.id) - const lastId = [...s].pop() - const fromIdx = ids.indexOf(lastId), toIdx = ids.indexOf(jobId) - if (fromIdx >= 0 && toIdx >= 0) { - const [lo, hi] = fromIdx < toIdx ? [fromIdx, toIdx] : [toIdx, fromIdx] - for (let i = lo; i <= hi; i++) s.add(ids[i]) - } - } else { - // Simple toggle (no Ctrl needed) - if (s.has(jobId)) s.delete(jobId); else s.add(jobId) - } - bottomSelected.value = s - } - function selectAllBottom () { const s = new Set(); unscheduledJobs.value.forEach(j => s.add(j.id)); bottomSelected.value = s } - function clearBottomSelect () { bottomSelected.value = new Set() } - function batchAssignBottom (techId) { - const dayStr = localDateStr(periodStart.value) - bottomSelected.value.forEach(jobId => { - const job = store.jobs.find(j => j.id === jobId) - if (job) { - pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants || [])] }) - smartAssign(job, techId, dayStr) - } - }) - bottomSelected.value = new Set() - invalidateRoutes() - } - - // ── Dispatch criteria ──────────────────────────────────────────────────────── - const defaultCriteria = [ - { id: 'urgency', label: 'Urgence (priorité haute en premier)', enabled: true }, - { id: 'balance', label: 'Équilibrage de charge (tech le moins chargé)', enabled: true }, - { id: 'proximity', label: 'Proximité géographique', enabled: true }, - { id: 'skills', label: 'Correspondance des tags/skills', enabled: false }, - ] - const dispatchCriteria = ref(JSON.parse(localStorage.getItem('sbv2-dispatchCriteria') || 'null') || defaultCriteria.map(c => ({ ...c }))) - const dispatchCriteriaModal = ref(false) - function saveDispatchCriteria () { localStorage.setItem('sbv2-dispatchCriteria', JSON.stringify(dispatchCriteria.value)); dispatchCriteriaModal.value = false } - function moveCriterion (idx, dir) { - const arr = dispatchCriteria.value, newIdx = idx + dir - if (newIdx < 0 || newIdx >= arr.length) return - const tmp = arr[idx]; arr[idx] = arr[newIdx]; arr[newIdx] = tmp - } - - // ── Column widths ──────────────────────────────────────────────────────────── - const btColWidths = ref(JSON.parse(localStorage.getItem('sbv2-btColW') || '{}')) - function btColW (col, def) { return (btColWidths.value[col] || def) + 'px' } - function startColResize (e, col) { - e.preventDefault(); e.stopPropagation() - const startX = e.clientX, startW = btColWidths.value[col] || parseInt(getComputedStyle(e.target.parentElement).width) - function onMove (ev) { btColWidths.value = { ...btColWidths.value, [col]: Math.max(40, startW + (ev.clientX - startX)) } } - function onUp () { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); localStorage.setItem('sbv2-btColW', JSON.stringify(btColWidths.value)) } - document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp) - } - - return { - bottomPanelOpen, bottomPanelH, unassignedGrouped, startBottomResize, - bottomSelected, toggleBottomSelect, selectAllBottom, clearBottomSelect, batchAssignBottom, - dispatchCriteria, dispatchCriteriaModal, saveDispatchCriteria, moveCriterion, - btColWidths, btColW, startColResize, - } -} diff --git a/apps/dispatch/src/composables/useDragDrop.js b/apps/dispatch/src/composables/useDragDrop.js deleted file mode 100644 index 0af0b1d..0000000 --- a/apps/dispatch/src/composables/useDragDrop.js +++ /dev/null @@ -1,242 +0,0 @@ -// ── Drag & Drop composable: job drag, tech drag, block move, block resize, batch drag ── -import { ref } from 'vue' -import { snapH, hToTime, fmtDur, localDateStr, SNAP, serializeAssistants } from './useHelpers' -import { updateJob } from 'src/api/dispatch' - -export function useDragDrop (deps) { - const { - store, pxPerHr, dayW, periodStart, periodDays, H_START, - getJobDate, bottomSelected, multiSelect, - pushUndo, smartAssign, invalidateRoutes, - } = deps - - const dragJob = ref(null) - const dragSrc = ref(null) - const dragIsAssist = ref(false) - const dropGhost = ref(null) - const dragTech = ref(null) - const dragBatchIds = ref(null) - - function cleanupDropIndicators () { - document.querySelectorAll('.sb-block-drop-hover').forEach(el => el.classList.remove('sb-block-drop-hover')) - dropGhost.value = null - } - - function onJobDragStart (e, job, srcTechId, isAssist = false) { - dragJob.value = job; dragSrc.value = srcTechId || null; dragIsAssist.value = isAssist - if (!srcTechId && bottomSelected.value.size > 1 && bottomSelected.value.has(job.id)) { - dragBatchIds.value = new Set(bottomSelected.value) - e.dataTransfer.setData('text/plain', `batch:${dragBatchIds.value.size}`) - } else { - dragBatchIds.value = null - } - e.dataTransfer.effectAllowed = 'move' - e.target.addEventListener('dragend', () => { cleanupDropIndicators(); dragIsAssist.value = false; dragBatchIds.value = null }, { once: true }) - } - - function onTimelineDragOver (e, tech) { - e.preventDefault() - if (!dragJob.value && !dragTech.value) return - const x = e.clientX - e.currentTarget.getBoundingClientRect().left - dropGhost.value = { techId: tech.id, x, dateStr: xToDateStr(x) } - } - - function onTimelineDragLeave (e) { - if (!e.currentTarget.contains(e.relatedTarget)) dropGhost.value = null - } - - function onTechDragStart (e, tech) { - dragTech.value = tech - e.dataTransfer.effectAllowed = 'copyMove' - e.dataTransfer.setData('text/plain', tech.id) - e.target.addEventListener('dragend', () => { dragTech.value = null; cleanupDropIndicators() }, { once: true }) - return tech - } - - function onBlockDrop (e, job) { - if (dragTech.value) { - e.preventDefault(); e.stopPropagation() - cleanupDropIndicators() - pushUndo({ type: 'removeAssistant', jobId: job.id, techId: dragTech.value.id, duration: 0, note: '' }) - store.addAssistant(job.id, dragTech.value.id) - dragTech.value = null - invalidateRoutes() - } - } - - function assignDroppedJob (tech, dateStr) { - if (!dragJob.value) return - if (dragIsAssist.value) { - dragJob.value = null; dragSrc.value = null; dragIsAssist.value = false; return - } - if (dragBatchIds.value && dragBatchIds.value.size > 1) { - const prevStates = [] - dragBatchIds.value.forEach(jobId => { - const j = store.jobs.find(x => x.id === jobId) - if (j && !j.assignedTech) { - prevStates.push({ jobId: j.id, techId: j.assignedTech, routeOrder: j.routeOrder, scheduledDate: j.scheduledDate, assistants: [...(j.assistants || [])] }) - smartAssign(j, tech.id, dateStr) - } - }) - if (prevStates.length) pushUndo({ type: 'batchAssign', assignments: prevStates, targetTechId: tech.id }) - bottomSelected.value = new Set() - dragBatchIds.value = null - } else if (multiSelect && multiSelect.value?.length > 1 && multiSelect.value.some(s => s.job.id === dragJob.value.id)) { - // Dragging a multi-selected block from timeline — move all selected - const prevStates = [] - const prevQueues = {} - store.technicians.forEach(t => { prevQueues[t.id] = [...t.queue] }) - multiSelect.value.filter(s => !s.isAssist).forEach(s => { - prevStates.push({ jobId: s.job.id, techId: s.job.assignedTech, routeOrder: s.job.routeOrder, scheduledDate: s.job.scheduledDate, assistants: [...(s.job.assistants || [])] }) - smartAssign(s.job, tech.id, dateStr) - }) - if (prevStates.length) pushUndo({ type: 'batchAssign', assignments: prevStates, prevQueues }) - multiSelect.value = [] - } else { - const job = dragJob.value - pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants || [])] }) - smartAssign(job, tech.id, dateStr) - } - dropGhost.value = null; dragJob.value = null; dragSrc.value = null - invalidateRoutes() - } - - function onTimelineDrop (e, tech) { - e.preventDefault() - cleanupDropIndicators() - - if (dragTech.value) { - const els = document.elementsFromPoint(e.clientX, e.clientY) - const blockEl = els.find(el => el.dataset?.jobId) - if (blockEl) { - const job = store.jobs.find(j => j.id === blockEl.dataset.jobId) - if (job) { - pushUndo({ type: 'removeAssistant', jobId: job.id, techId: dragTech.value.id, duration: 0, note: '' }) - store.addAssistant(job.id, dragTech.value.id) - dragTech.value = null; invalidateRoutes(); return - } - } - dragTech.value = null; return - } - - if (!dragJob.value) return - - if (dragJob.value.assignedTech === tech.id) { - const rect = e.currentTarget.getBoundingClientRect() - const x = (e.clientX || e.pageX) - rect.left - const dropH = H_START + x / pxPerHr.value - const dayStr = localDateStr(periodStart.value) - pushUndo({ type: 'optimizeRoute', techId: tech.id, prevQueue: [...tech.queue] }) - const draggedJob = dragJob.value - tech.queue = tech.queue.filter(j => j.id !== draggedJob.id) - const dayJobs = tech.queue.filter(j => getJobDate(j.id) === dayStr) - const queueDayStart = tech.queue.findIndex(j => getJobDate(j.id) === dayStr) - let slot = dayJobs.length, cursor = 8 - for (let i = 0; i < dayJobs.length; i++) { - const dur = parseFloat(dayJobs[i].duration) || 1 - if (dropH < cursor + dur / 2) { slot = i; break } - cursor += dur + 0.5 - } - const insertAt = queueDayStart >= 0 ? queueDayStart + slot : tech.queue.length - tech.queue.splice(insertAt, 0, draggedJob) - tech.queue.forEach((q, i) => { q.routeOrder = i; updateJob(q.name || q.id, { route_order: i }).catch(() => {}) }) - dragJob.value = null; dragSrc.value = null; invalidateRoutes(); return - } - - if (dragIsAssist.value) { - dragJob.value = null; dragSrc.value = null; dragIsAssist.value = false; return - } - - assignDroppedJob(tech, xToDateStr(e.clientX - e.currentTarget.getBoundingClientRect().left)) - } - - function onCalDrop (e, tech, dateStr) { assignDroppedJob(tech, dateStr) } - - function xToDateStr (x) { - const di = Math.max(0, Math.min(periodDays.value - 1, Math.floor(x / dayW.value))) - const d = new Date(periodStart.value); d.setDate(d.getDate() + di) - return localDateStr(d) - } - - function startBlockMove (e, job, block) { - if (e.button !== 0) return - const startX = e.clientX, startY = e.clientY - const startLeft = parseFloat(block.style.left) || 0 - let moving = false - function onMove (ev) { - const dx = ev.clientX - startX, dy = ev.clientY - startY - if (!moving && Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 5) { cleanup(); return } - if (!moving && Math.abs(dx) > 5) { moving = true; block.style.zIndex = '10' } - if (!moving) return - ev.preventDefault() - const newLeft = Math.max(0, startLeft + dx) - const newH = snapH(H_START + newLeft / pxPerHr.value) - block.style.left = ((newH - H_START) * pxPerHr.value) + 'px' - const meta = block.querySelector('.sb-block-meta') - if (meta) meta.textContent = `${hToTime(newH)} · ${fmtDur(job.duration)}` - } - function cleanup () { - document.removeEventListener('mousemove', onMove) - document.removeEventListener('mouseup', onUp) - } - function onUp (ev) { - cleanup() - if (!moving) return - block.style.zIndex = '' - const dx = ev.clientX - startX - const newH = snapH(H_START + Math.max(0, startLeft + dx) / pxPerHr.value) - job.startHour = newH; job.startTime = hToTime(newH) - store.setJobSchedule(job.id, job.scheduledDate, hToTime(newH)) - invalidateRoutes() - } - document.addEventListener('mousemove', onMove) - document.addEventListener('mouseup', onUp) - } - - function startResize (e, job, mode, assistTechId) { - e.preventDefault() - const startX = e.clientX - const startDur = mode === 'assist' - ? (job.assistants.find(a => a.techId === assistTechId)?.duration || job.duration) - : job.duration - const block = e.target.parentElement - const startW = block.offsetWidth - function onMove (ev) { - const dx = ev.clientX - startX - const newDur = Math.max(SNAP, snapH(Math.max(18, startW + dx) / pxPerHr.value)) - block.style.width = (newDur * pxPerHr.value) + 'px' - const meta = block.querySelector('.sb-block-meta') - if (meta) meta.textContent = mode === 'assist' ? `assistant · ${fmtDur(newDur)}` : fmtDur(newDur) - } - function onUp (ev) { - document.removeEventListener('mousemove', onMove) - document.removeEventListener('mouseup', onUp) - const dx = ev.clientX - startX - const newDur = Math.max(SNAP, snapH(Math.max(18, startW + dx) / pxPerHr.value)) - if (mode === 'assist' && assistTechId) { - const assist = job.assistants.find(a => a.techId === assistTechId) - if (assist) { - assist.duration = newDur - updateJob(job.name || job.id, { - assistants: serializeAssistants(job.assistants), - }).catch(() => {}) - } - } else { - job.duration = newDur - updateJob(job.name || job.id, { duration_h: newDur }).catch(() => {}) - } - invalidateRoutes() - } - document.addEventListener('mousemove', onMove) - document.addEventListener('mouseup', onUp) - } - - return { - dragJob, dragSrc, dragIsAssist, dropGhost, dragTech, dragBatchIds, - cleanupDropIndicators, - onJobDragStart, onTimelineDragOver, onTimelineDragLeave, - onTechDragStart, onBlockDrop, - assignDroppedJob, onTimelineDrop, onCalDrop, xToDateStr, - startBlockMove, startResize, - } -} diff --git a/apps/dispatch/src/composables/useHelpers.js b/apps/dispatch/src/composables/useHelpers.js deleted file mode 100644 index b916161..0000000 --- a/apps/dispatch/src/composables/useHelpers.js +++ /dev/null @@ -1,162 +0,0 @@ -// ── Pure utility functions (no Vue dependencies) ───────────────────────────── -export function localDateStr (d) { - return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` -} - -export function startOfWeek (d) { - const r = new Date(d); r.setHours(0,0,0,0) - const diff = r.getDay() === 0 ? -6 : 1 - r.getDay() - r.setDate(r.getDate() + diff); return r -} - -export function startOfMonth (d) { return new Date(d.getFullYear(), d.getMonth(), 1) } - -export function timeToH (t) { - const [h, m] = t.split(':').map(Number) - return h + m / 60 -} - -export function hToTime (h) { - const totalMin = Math.round(h * 60) - const hh = Math.floor(totalMin / 60) - const mm = totalMin % 60 - return `${String(hh).padStart(2,'0')}:${String(mm).padStart(2,'0')}` -} - -export function fmtDur (h) { - const totalMin = Math.round((parseFloat(h) || 0) * 60) - const hh = Math.floor(totalMin / 60) - const mm = totalMin % 60 - if (hh === 0) return `${mm}m` - if (mm === 0) return `${hh}h` - return `${hh}h${String(mm).padStart(2,'0')}` -} - -export const SNAP_MIN = 5 -export const SNAP = SNAP_MIN / 60 -export function snapH (h) { return Math.round(h * 60 / SNAP_MIN) * SNAP_MIN / 60 } - -export function dayLoadColor (ratio) { - const r = Math.min(ratio, 1.2) - if (r <= 0.5) return '#10b981' - if (r <= 0.75) return '#f59e0b' - if (r <= 1) return '#f97316' - return '#ef4444' -} - -export function shortAddr (addr) { - if (!addr) return '' - const parts = addr.replace(/[A-Z]\d[A-Z]\s?\d[A-Z]\d/g, '').trim().split(/[\s,]+/) - for (let i = parts.length - 1; i >= 0; i--) { - if (parts[i].length > 2 && /^[A-ZÀ-Ú]/.test(parts[i])) return parts[i] - } - return parts.slice(-2).join(' ') -} - -// Service colors & labels -export const SVC_COLORS = { 'Internet':'#3b82f6','Télévisión':'#a855f7','Téléphonie':'#10b981','Multi-service':'#f59e0b' } -export const SVC_ICONS = { 'Internet':'🌐','Télévisión':'📺','Téléphonie':'📞','Multi-service':'🔧' } -const SVC_CODES = { 'Internet':'WEB','Télévisión':'TV','Téléphonie':'TEL','Multi-service':'MX' } - -export function jobSvcCode (job) { - if (SVC_CODES[job.service_type]) return SVC_CODES[job.service_type] - const s = (job.subject || '').toLowerCase() - if (s.includes('internet')) return 'WEB' - if (s.includes('tv') || s.includes('télév')) return 'TV' - if (s.includes('téléph')) return 'TEL' - if (s.includes('multi')) return 'MX' - return 'WO' -} - -export function jobColor (job, techColors, store) { - if (SVC_COLORS[job.service_type]) return SVC_COLORS[job.service_type] - const s = (job.subject||'').toLowerCase() - if (s.includes('internet')) return '#3b82f6' - if (s.includes('tv')||s.includes('télév')) return '#a855f7' - if (s.includes('téléph')) return '#10b981' - if (s.includes('multi')) return '#f59e0b' - if (job.assignedTech && store) { - const t = store.technicians.find(x=>x.id===job.assignedTech) - if (t) return techColors[t.colorIdx] - } - return '#6b7280' -} - -export function jobSpansDate (job, ds) { - const start = job.scheduledDate - const end = job.endDate - if (!start) return false - if (!end) return start === ds - return ds >= start && ds <= end -} - -export function sortJobsByTime (jobs) { - return jobs.slice().sort((a, b) => { - const aH = a.startTime ? timeToH(a.startTime) : (a.startHour ?? 8) - const bH = b.startTime ? timeToH(b.startTime) : (b.startHour ?? 8) - return aH - bH - }) -} - -// Status helpers -export const STATUS_MAP = { - 'available': { cls:'st-available', label:'Disponible' }, - 'en-route': { cls:'st-enroute', label:'En route' }, - 'busy': { cls:'st-busy', label:'En cours' }, - 'in progress': { cls:'st-busy', label:'En cours' }, - 'off': { cls:'st-off', label:'Hors shift' }, -} -export function stOf (t) { return STATUS_MAP[(t.status||'').toLowerCase()] || STATUS_MAP['available'] } - -export function prioLabel (p) { return { high:'Haute', medium:'Moyenne', low:'Basse' }[p] || p || '—' } -export function prioClass (p) { return { high:'prio-high', medium:'prio-med', low:'prio-low' }[p] || '' } - -// Lucide-style inline SVG icons (stroke-based) -const _s = (d, w=10) => `${d}` - -export const ICON = { - pin: _s(''), - mapPin: _s(''), - wifi: _s(''), - tv: _s(''), - phone: _s(''), - wrench: _s(''), - cable: _s(''), - check: _s(''), - x: _s(''), - clock: _s(''), - loader: _s(''), - truck: _s(''), -} - -// Job type icon based on service/subject -export function jobTypeIcon (job) { - const s = (job.subject || '').toLowerCase() - const svc = job.service_type || '' - if (svc === 'Internet' || s.includes('internet') || s.includes('fibre') || s.includes('routeur') || s.includes('wifi')) return ICON.wifi - if (svc === 'Télévisión' || s.includes('tv') || s.includes('télév')) return ICON.tv - if (svc === 'Téléphonie' || s.includes('téléph') || s.includes('phone')) return ICON.phone - if (s.includes('cable') || s.includes('câble') || s.includes('cablage')) return ICON.cable - if (s.includes('camera') || s.includes('install')) return ICON.wrench - return ICON.wrench -} - -// Priority color -export function prioColor (p) { - return { high: '#ef4444', medium: '#f59e0b', low: '#7b80a0' }[p] || '#7b80a0' -} - -// Status icon (minimal, for timeline blocks) -// Serialize assistants array for ERPNext API calls (used in store + page) -export function serializeAssistants (assistants) { - return (assistants || []).map(a => ({ tech_id: a.techId, tech_name: a.techName, duration_h: a.duration, note: a.note || '', pinned: a.pinned ? 1 : 0 })) -} - -export function jobStatusIcon (job) { - const st = (job.status || '').toLowerCase() - if (st === 'completed') return { svg: ICON.check, cls: 'si-done' } - if (st === 'cancelled') return { svg: ICON.x, cls: 'si-cancelled' } - if (st === 'en-route') return { svg: ICON.truck, cls: 'si-enroute' } - if (st === 'in progress') return { svg: ICON.loader, cls: 'si-progress' } - return { svg: '', cls: '' } // no icon for open/assigned — the type icon is enough -} diff --git a/apps/dispatch/src/composables/useMap.js b/apps/dispatch/src/composables/useMap.js deleted file mode 100644 index 8529f1e..0000000 --- a/apps/dispatch/src/composables/useMap.js +++ /dev/null @@ -1,413 +0,0 @@ -// ── Map composable: Mapbox GL map, markers, routes, geo-fix, map-drag ──────── -import { ref, watch, nextTick } from 'vue' -import { localDateStr, jobSpansDate, jobSvcCode, SVC_COLORS } from './useHelpers' - -export function useMap (deps) { - const { - store, MAPBOX_TOKEN, TECH_COLORS, - currentView, periodStart, filteredResources, mapVisible, - routeLegs, routeGeometry, - getJobDate, jobColor, pushUndo, smartAssign, invalidateRoutes, - dragJob, dragIsAssist, rightPanel, openCtxMenu, - } = deps - - let map = null - let mapResizeObs = null - const mapContainer = ref(null) - const selectedTechId = ref(null) - const mapMarkers = ref([]) - const mapPanelW = ref(parseInt(localStorage.getItem('sbv2-mapW')) || 340) - const geoFixJob = ref(null) - const mapDragJob = ref(null) - let _mapGhost = null - - // ── Geo-fix ────────────────────────────────────────────────────────────────── - function startGeoFix (job) { - geoFixJob.value = job - if (!mapVisible.value) mapVisible.value = true - if (map) map.getCanvas().style.cursor = 'crosshair' - } - function cancelGeoFix () { - geoFixJob.value = null - if (map) map.getCanvas().style.cursor = '' - } - watch(geoFixJob, v => { if (map) map.getCanvas().style.cursor = v ? 'crosshair' : '' }) - - // ── Panel resize ───────────────────────────────────────────────────────────── - function startMapResize (e) { - e.preventDefault() - const startX = e.clientX, startW = mapPanelW.value - function onMove (ev) { - mapPanelW.value = Math.max(220, Math.min(window.innerWidth * 0.65, startW - (ev.clientX - startX))) - } - function onUp () { - document.removeEventListener('mousemove', onMove) - document.removeEventListener('mouseup', onUp) - localStorage.setItem('sbv2-mapW', String(mapPanelW.value)) - if (map) map.resize() - } - document.addEventListener('mousemove', onMove) - document.addEventListener('mouseup', onUp) - } - - // ── Init ───────────────────────────────────────────────────────────────────── - async function initMap () { - if (!mapContainer.value || map) return - if (!window.mapboxgl) { - if (!document.getElementById('mapbox-js')) { - await new Promise(resolve => { - const s = document.createElement('script'); s.id = 'mapbox-js' - s.src = 'https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js' - s.onload = resolve; document.head.appendChild(s) - }) - } else { await new Promise(r => setTimeout(r, 200)) } - } - const mapboxgl = window.mapboxgl - mapboxgl.accessToken = MAPBOX_TOKEN - map = new mapboxgl.Map({ - container: mapContainer.value, - style: 'mapbox://styles/mapbox/dark-v11', - center: [-73.567, 45.502], zoom: 10, - }) - if (mapResizeObs) mapResizeObs.disconnect() - mapResizeObs = new ResizeObserver(() => { if (map) map.resize() }) - mapResizeObs.observe(mapContainer.value) - - map.on('load', () => { - map.resize() - // Route layers - map.addSource('sb-route', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }) - map.addLayer({ id: 'sb-route-halo', type: 'line', source: 'sb-route', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': '#6366f1', 'line-width': 12, 'line-opacity': 0.18 } }) - map.addLayer({ id: 'sb-route-line', type: 'line', source: 'sb-route', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': '#6366f1', 'line-width': 3.5, 'line-opacity': 0.85 } }) - // Job layers - map.addSource('sb-jobs', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }) - map.addLayer({ id: 'sb-jobs-halo', type: 'circle', source: 'sb-jobs', paint: { 'circle-radius': 22, 'circle-color': ['get', 'color'], 'circle-opacity': ['*', ['get', 'opacity'], 0.18], 'circle-blur': 0.7 } }) - map.addLayer({ id: 'sb-jobs-circle', type: 'circle', source: 'sb-jobs', paint: { 'circle-radius': 15, 'circle-color': ['get', 'color'], 'circle-opacity': ['get', 'opacity'], 'circle-stroke-width': 2, 'circle-stroke-color': ['case', ['get', 'unassigned'], 'rgba(255,255,255,0.4)', 'rgba(255,255,255,0.85)'], 'circle-stroke-opacity': ['get', 'opacity'] } }) - map.addLayer({ id: 'sb-jobs-label', type: 'symbol', source: 'sb-jobs', layout: { 'text-field': ['get', 'label'], 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'], 'text-size': 9, 'text-allow-overlap': true, 'text-ignore-placement': true }, paint: { 'text-color': '#ffffff', 'text-opacity': ['get', 'opacity'] } }) - - // Event handlers - map.on('mouseenter', 'sb-jobs-circle', () => { if (!mapDragJob.value && !geoFixJob.value) map.getCanvas().style.cursor = 'grab' }) - map.on('mouseleave', 'sb-jobs-circle', () => { if (!mapDragJob.value && !geoFixJob.value) map.getCanvas().style.cursor = '' }) - map.on('mousedown', 'sb-jobs-circle', e => { - if (geoFixJob.value) return - e.preventDefault() - const job = store.jobs.find(j => j.id === e.features[0].properties.id) - if (job) startMapDrag(e.originalEvent, job) - }) - map.on('click', 'sb-jobs-circle', e => { - if (geoFixJob.value) return - const job = store.jobs.find(j => j.id === e.features[0].properties.id) - if (job) { - const tech = job.assignedTech ? store.technicians.find(t => t.id === job.assignedTech) : null - rightPanel.value = { mode: 'details', data: { job, tech } } - } - }) - map.on('contextmenu', 'sb-jobs-circle', e => { - const job = store.jobs.find(j => j.id === e.features[0].properties.id) - if (job) { - const tech = job.assignedTech ? store.technicians.find(t => t.id === job.assignedTech) : null - openCtxMenu(e.originalEvent, job, tech?.id || null) - } - }) - map.on('mouseenter', 'sb-route-line', () => { if (mapDragJob.value) map.getCanvas().style.cursor = 'copy' }) - map.on('mouseleave', 'sb-route-line', () => { if (!mapDragJob.value) map.getCanvas().style.cursor = '' }) - - // Geo-fix click - map.on('click', e => { - if (!geoFixJob.value) return - const job = geoFixJob.value - const saved = JSON.parse(localStorage.getItem('dispatch-job-coords') || '{}') - saved[job.id] = [e.lngLat.lng, e.lngLat.lat] - localStorage.setItem('dispatch-job-coords', JSON.stringify(saved)) - store.updateJobCoords(job.id, e.lngLat.lng, e.lngLat.lat) - routeLegs.value = {}; routeGeometry.value = {} - geoFixJob.value = null - map.getCanvas().style.cursor = '' - nextTick(() => { - drawMapMarkers() - const dayStr = localDateStr(periodStart.value) - filteredResources.value.forEach(tech => computeDayRoute(tech, dayStr)) - drawSelectedRoute() - }) - }) - - drawMapMarkers() - drawSelectedRoute() - }) - } - - // ── Draw markers ───────────────────────────────────────────────────────────── - function drawMapMarkers () { - if (!map || !window.mapboxgl) return - const dayStr = localDateStr(periodStart.value) - const mbgl = window.mapboxgl - - const jobFeatures = store.jobs - .filter(j => j.coords && !(j.coords[0] === 0 && j.coords[1] === 0)) - .filter(j => { - if (!j.assignedTech) return (j.scheduledDate || null) === dayStr - return jobSpansDate(j, dayStr) - }) - .map(job => { - const isUnassigned = !job.assignedTech - const isCompleted = (job.status || '').toLowerCase() === 'completed' - const isSelected = selectedTechId.value && job.assignedTech === selectedTechId.value - const opacity = isCompleted ? 0.4 : (isSelected || isUnassigned || !selectedTechId.value ? 0.92 : 0.4) - let label = jobSvcCode(job) - if (!isUnassigned) { - const tech = store.technicians.find(t => t.id === job.assignedTech) - if (tech) { const idx = tech.queue.filter(j2 => getJobDate(j2.id) === dayStr).indexOf(job); if (idx >= 0) label = String(idx + 1) } - } - return { type: 'Feature', geometry: { type: 'Point', coordinates: job.coords }, properties: { id: job.id, color: jobColor(job), label, title: job.subject, opacity, unassigned: isUnassigned, completed: isCompleted } } - }) - if (map.getSource('sb-jobs')) map.getSource('sb-jobs').setData({ type: 'FeatureCollection', features: jobFeatures }) - - // Tech avatar markers - mapMarkers.value.forEach(m => m.remove()) - mapMarkers.value = [] - - // Pre-compute: which techs are assistants on which lead tech's jobs today - const groupCounts = {} // leadTechId → total crew size (1 + assistants) - store.technicians.forEach(tech => { - const todayJobs = tech.queue.filter(j => jobSpansDate(j, dayStr)) - const assistIds = new Set() - todayJobs.forEach(j => (j.assistants || []).forEach(a => assistIds.add(a.techId))) - if (assistIds.size > 0) groupCounts[tech.id] = 1 + assistIds.size - }) - - filteredResources.value.forEach(tech => { - const pos = tech.gpsCoords || tech.coords - if (!pos || (pos[0] === 0 && pos[1] === 0)) return - const initials = tech.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) - const color = TECH_COLORS[tech.colorIdx] - - // Calculate daily workload + completion - const todayJobs = tech.queue.filter(j => jobSpansDate(j, dayStr)) - const todayAssist = (tech.assistJobs || []).filter(j => jobSpansDate(j, dayStr)) - const allToday = [...todayJobs, ...todayAssist] - const totalHours = allToday.reduce((s, j) => s + (j.duration || 1), 0) - const doneHours = allToday.filter(j => (j.status || '').toLowerCase() === 'completed') - .reduce((s, j) => s + (j.duration || 1), 0) - const loadPct = Math.min(totalHours / 8, 1) - const donePct = totalHours > 0 ? Math.min(doneHours / 8, 1) : 0 - const loadColor = loadPct < 0.5 ? '#10b981' : loadPct < 0.75 ? '#f59e0b' : loadPct < 0.9 ? '#f97316' : '#ef4444' - - // Ring + avatar in a fixed-size container so Mapbox anchor stays consistent - const PIN = 36, STROKE = 3.5, SIZE = PIN + STROKE * 2 + 2 // ~45px - const R = (SIZE - STROKE) / 2, CIRC = 2 * Math.PI * R - const completedJobs = allToday.filter(j => (j.status || '').toLowerCase() === 'completed').length - const totalJobs = allToday.length - const completionPct = totalJobs > 0 ? completedJobs / totalJobs : 0 - - // Fixed-size outer wrapper — Mapbox anchors to this - const outer = document.createElement('div') - outer.style.cssText = `cursor:pointer;width:${SIZE}px;height:${SIZE}px;position:relative;` - outer.dataset.techId = tech.id - - // SVG ring (load arc + completion arc) — fills entire container - if (totalHours > 0) { - const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') - svg.setAttribute('width', SIZE); svg.setAttribute('height', SIZE) - svg.style.cssText = 'position:absolute;top:0;left:0;transform:rotate(-90deg);pointer-events:none;' - const loadArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle') - loadArc.setAttribute('cx', SIZE/2); loadArc.setAttribute('cy', SIZE/2); loadArc.setAttribute('r', R) - loadArc.setAttribute('fill', 'none'); loadArc.setAttribute('stroke', loadColor) - loadArc.setAttribute('stroke-width', STROKE); loadArc.setAttribute('opacity', '0.3') - loadArc.setAttribute('stroke-dasharray', `${CIRC * loadPct} ${CIRC}`) - loadArc.setAttribute('stroke-linecap', 'round') - svg.appendChild(loadArc) - if (completionPct > 0) { - const doneArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle') - doneArc.setAttribute('cx', SIZE/2); doneArc.setAttribute('cy', SIZE/2); doneArc.setAttribute('r', R) - doneArc.setAttribute('fill', 'none'); doneArc.setAttribute('stroke', '#10b981') - doneArc.setAttribute('stroke-width', STROKE); doneArc.setAttribute('opacity', '1') - doneArc.setAttribute('stroke-dasharray', `${CIRC * completionPct * loadPct} ${CIRC}`) - doneArc.setAttribute('stroke-linecap', 'round') - svg.appendChild(doneArc) - } - outer.appendChild(svg) - } - - // Avatar circle — absolutely centered in container - const el = document.createElement('div') - el.className = 'sb-map-tech-pin' - const offset = (SIZE - PIN) / 2 - el.style.cssText = `background:${color};border-color:${color};position:absolute;top:${offset}px;left:${offset}px;width:${PIN}px;height:${PIN}px;` - el.textContent = initials - el.title = `${tech.fullName} — ${completedJobs}/${totalJobs} jobs (${doneHours.toFixed(1)}h / ${totalHours.toFixed(1)}h)` - outer.appendChild(el) - - // Group badge (crew size) - const crew = groupCounts[tech.id] - if (crew && crew > 1) { - const badge = document.createElement('div') - badge.className = 'sb-map-crew-badge' - badge.textContent = String(crew) - badge.title = `Équipe de ${crew}` - el.appendChild(badge) - } - - // Drag & drop handlers - outer.addEventListener('dragover', e => { e.preventDefault(); el.style.transform = 'scale(1.25)' }) - outer.addEventListener('dragleave', () => { el.style.transform = '' }) - outer.addEventListener('drop', e => { - e.preventDefault(); el.style.transform = '' - const job = dragJob.value - if (job) { - pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants || [])] }) - smartAssign(job, tech.id, dayStr) - dragJob.value = null - invalidateRoutes() - } - }) - outer.addEventListener('mouseenter', () => { if (mapDragJob.value) el.style.transform = 'scale(1.3)' }) - outer.addEventListener('mouseleave', () => { el.style.transform = '' }) - - if (tech.gpsCoords) { - el.classList.add('sb-map-gps-active') - el.title += ' (GPS)' - } - const m = new mbgl.Marker({ element: outer, anchor: 'center' }).setLngLat(pos).addTo(map) - mapMarkers.value.push(m) - }) - } - - // ── Map drag (job pin → tech) ──────────────────────────────────────────────── - function startMapDrag (e, job) { - e.preventDefault() - mapDragJob.value = job - if (map) map.dragPan.disable() - _mapGhost = document.createElement('div') - _mapGhost.className = 'sb-map-drag-ghost' - _mapGhost.textContent = job.subject - _mapGhost.style.cssText = `position:fixed;pointer-events:none;z-index:9999;left:${e.clientX + 14}px;top:${e.clientY + 14}px` - document.body.appendChild(_mapGhost) - document.addEventListener('mousemove', _onMapDragMove) - document.addEventListener('mouseup', _onMapDragEnd) - } - function _onMapDragMove (e) { if (_mapGhost) { _mapGhost.style.left = (e.clientX + 14) + 'px'; _mapGhost.style.top = (e.clientY + 14) + 'px' } } - function _onMapDragEnd (e) { - document.removeEventListener('mousemove', _onMapDragMove) - document.removeEventListener('mouseup', _onMapDragEnd) - if (_mapGhost) { _mapGhost.remove(); _mapGhost = null } - if (map) { map.getCanvas().style.cursor = ''; map.dragPan.enable() } - const job = mapDragJob.value; mapDragJob.value = null - if (!job) return - const els = document.elementsFromPoint(e.clientX, e.clientY) - const dateStr = localDateStr(periodStart.value) - function assignFromMap (tech) { - if (dragIsAssist.value) { dragIsAssist.value = false; return } - pushUndo({ type: 'unassignJob', jobId: job.id, techId: job.assignedTech, routeOrder: job.routeOrder, scheduledDate: job.scheduledDate, assistants: [...(job.assistants || [])] }) - smartAssign(job, tech.id, dateStr) - invalidateRoutes() - } - const domTarget = els.find(el => el.dataset?.techId) - if (domTarget) { const tech = store.technicians.find(t => t.id === domTarget.dataset.techId); if (tech) assignFromMap(tech); return } - if (map && selectedTechId.value) { - const canvas = map.getCanvas(), rect = canvas.getBoundingClientRect() - if (e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom) { - const tech = store.technicians.find(t => t.id === selectedTechId.value) - if (tech) assignFromMap(tech) - } - } - } - - // ── Route computation ──────────────────────────────────────────────────────── - async function computeDayRoute (tech, dateStr) { - const key = `${tech.id}||${dateStr}` - if (routeLegs.value[key] !== undefined) return - const points = [] - if (tech.coords?.[0] && tech.coords?.[1]) points.push(`${tech.coords[0]},${tech.coords[1]}`) - const allJobs = [...tech.queue.filter(j => jobSpansDate(j, dateStr)), ...(tech.assistJobs || []).filter(j => jobSpansDate(j, dateStr))] - allJobs.forEach(j => { if (j.coords && (j.coords[0] !== 0 || j.coords[1] !== 0)) points.push(`${j.coords[0]},${j.coords[1]}`) }) - function setCache (legs, geom) { - routeLegs.value = { ...routeLegs.value, [key]: legs } - routeGeometry.value = { ...routeGeometry.value, [key]: geom } - } - if (points.length < 2) { setCache([], null); return } - try { - const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${points.join(';')}?overview=full&geometries=geojson&access_token=${MAPBOX_TOKEN}` - const r = await fetch(url) - if (!r.ok) throw new Error(`HTTP ${r.status}`) - const data = await r.json() - if (data.routes?.[0]) setCache(data.routes[0].legs.map(l => Math.round(l.duration / 60)), data.routes[0].geometry.coordinates) - else setCache([], null) - } catch (e) { console.warn('[route] fetch error', e); setCache([], null) } - } - - // ── Draw route ─────────────────────────────────────────────────────────────── - function drawSelectedRoute () { - if (!map || !mapVisible.value) return - const src = map.getSource('sb-route'); if (!src) return - const empty = { type: 'FeatureCollection', features: [] } - if (currentView.value !== 'day') { src.setData(empty); return } - const dayStr = localDateStr(periodStart.value) - const features = [] - const techs = selectedTechId.value ? filteredResources.value.filter(t => t.id === selectedTechId.value) : filteredResources.value - techs.forEach(tech => { - const coords = routeGeometry.value[`${tech.id}||${dayStr}`] - if (coords?.length) features.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: coords }, properties: { color: TECH_COLORS[tech.colorIdx] } }) - }) - src.setData({ type: 'FeatureCollection', features }) - map.setPaintProperty('sb-route-halo', 'line-color', ['get', 'color']) - map.setPaintProperty('sb-route-line', 'line-color', ['get', 'color']) - } - - // ── Select tech on board ───────────────────────────────────────────────────── - function selectTechOnBoard (tech) { - const wasSelected = selectedTechId.value === tech.id - selectedTechId.value = wasSelected ? null : tech.id - if (!wasSelected && currentView.value === 'day') { - if (!mapVisible.value) { - mapPanelW.value = Math.round(window.innerWidth * 0.5) - localStorage.setItem('sbv2-mapW', String(mapPanelW.value)) - mapVisible.value = true - } - } - if (map) { drawMapMarkers(); drawSelectedRoute() } - } - - // ── Watchers ───────────────────────────────────────────────────────────────── - watch([selectedTechId, () => periodStart.value?.getTime(), currentView, routeGeometry], () => { if (map) { drawMapMarkers(); drawSelectedRoute() } }) - watch(mapVisible, async v => { - if (v) { - if (map) { try { map.remove() } catch (_) {} map = null } - await nextTick(); await initMap() - if (map) { - const r = () => { if (!map) return; map.resize(); drawMapMarkers(); drawSelectedRoute() } - await nextTick(); r(); setTimeout(r, 100); setTimeout(r, 300); setTimeout(r, 600) - } - } else { - if (mapResizeObs) { mapResizeObs.disconnect(); mapResizeObs = null } - if (map) { try { map.remove() } catch (_) {} map = null } - } - }) - watch([() => periodStart.value?.getTime(), filteredResources], () => { - if (currentView.value === 'day' && mapVisible.value && map) { drawMapMarkers(); drawSelectedRoute() } - }) - watch( - () => store.technicians.map(t => t.gpsCoords), - () => { if (map) drawMapMarkers() }, - { deep: true } - ) - - // ── Lifecycle helpers ──────────────────────────────────────────────────────── - function destroyMap () { - if (map) { map.remove(); map = null } - if (mapResizeObs) { mapResizeObs.disconnect(); mapResizeObs = null } - } - function loadMapboxCss () { - if (!document.getElementById('mapbox-css')) { - const l = document.createElement('link'); l.id = 'mapbox-css'; l.rel = 'stylesheet' - l.href = 'https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css'; document.head.appendChild(l) - } - } - function getMap () { return map } - - return { - mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, mapDragJob, - startGeoFix, cancelGeoFix, startMapResize, initMap, - drawMapMarkers, drawSelectedRoute, computeDayRoute, - selectTechOnBoard, destroyMap, loadMapboxCss, getMap, - } -} diff --git a/apps/dispatch/src/composables/useScheduler.js b/apps/dispatch/src/composables/useScheduler.js deleted file mode 100644 index d872f59..0000000 --- a/apps/dispatch/src/composables/useScheduler.js +++ /dev/null @@ -1,209 +0,0 @@ -// ── Scheduling logic: timeline computation, route cache, job placement ─────── -import { ref, computed } from 'vue' -import { localDateStr, timeToH, hToTime, sortJobsByTime, jobSpansDate } from './useHelpers' - -export function useScheduler (store, currentView, periodStart, periodDays, dayColumns, pxPerHr, getJobDate, getJobTime, jobColorFn) { - const H_START = 7 - const H_END = 20 - - // ── Route cache ──────────────────────────────────────────────────────────── - const routeLegs = ref({}) - const routeGeometry = ref({}) - - // ── Parent start position cache ──────────────────────────────────────────── - let _parentStartCache = {} - - function getParentStartH (job) { - if (!store.technicians.length) return job.startHour ?? 8 - const key = `${job.assignedTech}||${job.id}` - if (_parentStartCache[key] !== undefined) return _parentStartCache[key] - const leadTech = store.technicians.find(t => t.id === job.assignedTech) - if (!leadTech) return job.startHour ?? 8 - const dayStr = localDateStr(periodStart.value) - const leadJobs = sortJobsByTime(leadTech.queue.filter(j => getJobDate(j.id) === dayStr)) - const cacheKey = `${leadTech.id}||${dayStr}` - const legMins = routeLegs.value[cacheKey] - const hasHome = !!(leadTech.coords?.[0] && leadTech.coords?.[1]) - let cursor = 8, result = job.startHour ?? 8 - leadJobs.forEach((j, idx) => { - const showTravel = idx > 0 || (idx === 0 && hasHome) - if (showTravel) { - const legIdx = hasHome ? idx : idx - 1 - const routeMin = legMins?.[legIdx] - cursor += (routeMin != null ? routeMin : (parseFloat(j.legDur) > 0 ? parseFloat(j.legDur) : 20)) / 60 - } - const pinnedH = j.startTime ? timeToH(j.startTime) : null - const startH = pinnedH ?? cursor - if (j.id === job.id) result = startH - cursor = startH + (parseFloat(j.duration) || 1) - }) - _parentStartCache[key] = result - return result - } - - // ── All jobs for a tech on a date (primary + assists) ────────────────────── - function techAllJobsForDate (tech, dateStr) { - _parentStartCache = {} - const primary = tech.queue.filter(j => jobSpansDate(j, dateStr)) - const assists = (tech.assistJobs || []) - .filter(j => jobSpansDate(j, dateStr)) - .map(j => { - const a = j.assistants.find(x => x.techId === tech.id) - const parentH = getParentStartH(j) - return { - ...j, - duration: a?.duration || j.duration, - startTime: hToTime(parentH), - startHour: parentH, - _isAssist: true, - _assistPinned: !!a?.pinned, - _assistNote: a?.note || '', - _parentJob: j, - } - }) - return sortJobsByTime([...primary, ...assists]) - } - - // ── Day view: schedule blocks with pinned anchors + auto-flow ────────────── - function techDayJobsWithTravel (tech) { - const dayStr = localDateStr(periodStart.value) - const cacheKey = `${tech.id}||${dayStr}` - const legMins = routeLegs.value[cacheKey] - const hasHome = !!(tech.coords?.[0] && tech.coords?.[1]) - const allJobs = techAllJobsForDate(tech, dayStr) - - const flowEntries = [] - const floatingEntries = [] - allJobs.forEach(job => { - const isAssist = !!job._isAssist - const dur = parseFloat(job.duration) || 1 - const isPinned = isAssist ? !!job._assistPinned : !!getJobTime(job.id) - const pinH = isAssist ? job.startHour : (getJobTime(job.id) ? timeToH(getJobTime(job.id)) : null) - const entry = { job, dur, isAssist, isPinned, pinH } - if (isAssist && !job._assistPinned) floatingEntries.push(entry) - else flowEntries.push(entry) - }) - - const pinnedAnchors = flowEntries.filter(e => e.isPinned).map(e => ({ start: e.pinH, end: e.pinH + e.dur })) - const placed = [] - const occupied = pinnedAnchors.map(a => ({ ...a })) - const sortedFlow = [...flowEntries].sort((a, b) => { - if (a.isPinned && b.isPinned) return a.pinH - b.pinH - if (a.isPinned) return -1 - if (b.isPinned) return 1 - return 0 - }) - - sortedFlow.filter(e => e.isPinned).forEach(e => placed.push({ entry: e, startH: e.pinH })) - - let cursor = 8, flowIdx = 0 - sortedFlow.filter(e => !e.isPinned).forEach(e => { - const legIdx = hasHome ? flowIdx : flowIdx - 1 - const routeMin = legMins?.[legIdx >= 0 ? legIdx : 0] - const travelH = (routeMin != null ? routeMin : (parseFloat(e.job.legDur) > 0 ? parseFloat(e.job.legDur) : 20)) / 60 - let startH = cursor + (flowIdx > 0 || hasHome ? travelH : 0) - let safe = false - while (!safe) { - const endH = startH + e.dur - const overlap = occupied.find(o => startH < o.end && endH > o.start) - if (overlap) startH = overlap.end + travelH - else safe = true - } - placed.push({ entry: e, startH }) - occupied.push({ start: startH, end: startH + e.dur }) - cursor = startH + e.dur - flowIdx++ - }) - - placed.sort((a, b) => a.startH - b.startH) - - const result = [] - let prevEndH = null - placed.forEach((p, pIdx) => { - const { entry, startH } = p - const { job, dur, isAssist, isPinned } = entry - const realJob = isAssist ? job._parentJob : job - const travelStart = prevEndH ?? (hasHome ? 8 : null) - if (travelStart != null && startH > travelStart + 0.01) { - const gapH = startH - travelStart - const legIdx = hasHome ? pIdx : pIdx - 1 - const routeMin = legMins?.[legIdx >= 0 ? legIdx : 0] - const fromRoute = routeMin != null - result.push({ - type: 'travel', job: realJob, travelMin: fromRoute ? routeMin : Math.round(gapH * 60), fromRoute, isAssist: false, - style: { left: (travelStart - H_START) * pxPerHr.value + 'px', width: Math.max(8, gapH * pxPerHr.value) + 'px', top: '10px', bottom: '10px', position: 'absolute' }, - color: jobColorFn(realJob), - }) - } - const jLeft = (startH - H_START) * pxPerHr.value - const jWidth = Math.max(18, dur * pxPerHr.value) - result.push({ - type: isAssist ? 'assist' : 'job', job: realJob, - pinned: isPinned, pinnedTime: isPinned ? hToTime(startH) : null, isAssist, - assistPinned: isAssist ? isPinned : false, assistDur: isAssist ? dur : null, - assistNote: isAssist ? job._assistNote : null, assistTechId: isAssist ? tech.id : null, - style: { left: jLeft + 'px', width: jWidth + 'px', top: '6px', bottom: '6px', position: 'absolute' }, - }) - prevEndH = startH + dur - }) - - floatingEntries.forEach(entry => { - const { job, dur } = entry - const startH = job.startHour ?? 8 - result.push({ - type: 'assist', job: job._parentJob, pinned: false, pinnedTime: null, isAssist: true, - assistPinned: false, assistDur: dur, assistNote: job._assistNote, assistTechId: tech.id, - style: { left: (startH - H_START) * pxPerHr.value + 'px', width: Math.max(18, dur * pxPerHr.value) + 'px', top: '52%', bottom: '4px', position: 'absolute' }, - }) - }) - - return result - } - - // ── Week view helpers ────────────────────────────────────────────────────── - function techBookingsByDay (tech) { - return dayColumns.value.map(d => { - const ds = localDateStr(d) - const primary = tech.queue.filter(j => jobSpansDate(j, ds)) - const assists = (tech.assistJobs || []) - .filter(j => jobSpansDate(j, ds) && j.assistants.find(a => a.techId === tech.id)?.pinned) - .map(j => ({ ...j, _isAssistChip: true, _assistDur: j.assistants.find(a => a.techId === tech.id)?.duration || j.duration })) - return { day: d, dateStr: ds, jobs: [...primary, ...assists] } - }) - } - - function periodLoadH (tech) { - const dateSet = new Set(dayColumns.value.map(d => localDateStr(d))) - let total = tech.queue.reduce((sum, j) => { - const ds = getJobDate(j.id) - return ds && dateSet.has(ds) ? sum + (parseFloat(j.duration) || 0) : sum - }, 0) - ;(tech.assistJobs || []).forEach(j => { - const ds = getJobDate(j.id) - if (ds && dateSet.has(ds)) { - const a = j.assistants.find(x => x.techId === tech.id) - if (a?.pinned) total += parseFloat(a?.duration || j.duration) || 0 - } - }) - return total - } - - function techsActiveOnDay (dateStr, resources) { - return resources.filter(tech => - tech.queue.some(j => jobSpansDate(j, dateStr)) || - (tech.assistJobs || []).some(j => jobSpansDate(j, dateStr) && j.assistants.find(a => a.techId === tech.id)?.pinned) - ) - } - - function dayJobCount (dateStr, resources) { - const jobIds = new Set() - resources.forEach(t => t.queue.filter(j => jobSpansDate(j, dateStr)).forEach(j => jobIds.add(j.id))) - return jobIds.size - } - - return { - H_START, H_END, routeLegs, routeGeometry, - techAllJobsForDate, techDayJobsWithTravel, - techBookingsByDay, periodLoadH, techsActiveOnDay, dayJobCount, - } -} diff --git a/apps/dispatch/src/composables/useSelection.js b/apps/dispatch/src/composables/useSelection.js deleted file mode 100644 index 3e3a922..0000000 --- a/apps/dispatch/src/composables/useSelection.js +++ /dev/null @@ -1,172 +0,0 @@ -// ── Selection composable: lasso, multi-select, hover linking, batch ops ─────── -import { ref, computed } from 'vue' -import { localDateStr } from './useHelpers' - -export function useSelection (deps) { - const { store, periodStart, smartAssign, invalidateRoutes, fullUnassign } = deps - - const hoveredJobId = ref(null) - const selectedJob = ref(null) // { job, techId, isAssist?, assistTechId? } - const multiSelect = ref([]) // [{ job, techId, isAssist?, assistTechId? }] - - // ── Select / toggle ───────────────────────────────────────────────────────── - function selectJob (job, techId, isAssist = false, assistTechId = null, event = null, rightPanel = null) { - const entry = { job, techId, isAssist, assistTechId } - const isMulti = event && (event.ctrlKey || event.metaKey) - if (isMulti) { - const idx = multiSelect.value.findIndex(s => s.job.id === job.id && s.isAssist === isAssist) - if (idx >= 0) multiSelect.value.splice(idx, 1) - else multiSelect.value.push(entry) - selectedJob.value = entry - } else { - multiSelect.value = [] - const same = selectedJob.value?.job?.id === job.id && selectedJob.value?.isAssist === isAssist && selectedJob.value?.assistTechId === assistTechId - selectedJob.value = same ? null : entry - if (!same && rightPanel !== undefined) { - const tech = store.technicians.find(t => t.id === (techId || job.assignedTech)) - if (rightPanel !== null && typeof rightPanel === 'object' && 'value' in rightPanel) { - rightPanel.value = { mode: 'details', data: { job, tech: tech || null } } - } - } else if (rightPanel !== null && typeof rightPanel === 'object' && 'value' in rightPanel) { - rightPanel.value = null - } - } - } - - function isJobMultiSelected (jobId, isAssist = false) { - return multiSelect.value.some(s => s.job.id === jobId && s.isAssist === isAssist) - } - - // ── Batch ops (grouped undo) ────────────────────────────────────────────────── - function batchUnassign (pushUndo) { - if (!multiSelect.value.length) return - // Snapshot all jobs before unassign — single undo entry - const assignments = multiSelect.value.filter(s => !s.isAssist).map(s => ({ - jobId: s.job.id, techId: s.job.assignedTech, routeOrder: s.job.routeOrder, - scheduledDate: s.job.scheduledDate, assistants: [...(s.job.assistants || [])] - })) - const prevQueues = {} - store.technicians.forEach(t => { prevQueues[t.id] = [...t.queue] }) - - multiSelect.value.forEach(s => { - if (s.isAssist && s.assistTechId) store.removeAssistant(s.job.id, s.assistTechId) - else store.fullUnassign(s.job.id) - }) - - if (pushUndo && assignments.length) { - pushUndo({ type: 'batchAssign', assignments, prevQueues }) - } - multiSelect.value = []; selectedJob.value = null - invalidateRoutes() - } - - function batchMoveTo (techId, dayStr, pushUndo) { - if (!multiSelect.value.length) return - const day = dayStr || localDateStr(periodStart.value) - const jobs = multiSelect.value.filter(s => !s.isAssist) - // Snapshot for grouped undo - const assignments = jobs.map(s => ({ - jobId: s.job.id, techId: s.job.assignedTech, routeOrder: s.job.routeOrder, - scheduledDate: s.job.scheduledDate, assistants: [...(s.job.assistants || [])] - })) - const prevQueues = {} - store.technicians.forEach(t => { prevQueues[t.id] = [...t.queue] }) - - jobs.forEach(s => smartAssign(s.job, techId, day)) - - if (pushUndo && assignments.length) { - pushUndo({ type: 'batchAssign', assignments, prevQueues }) - } - multiSelect.value = []; selectedJob.value = null - invalidateRoutes() - } - - // ── Lasso ───────────────────────────────────────────────────────────────────── - const lasso = ref(null) - const boardScroll = ref(null) - - const lassoStyle = computed(() => { - if (!lasso.value) return {} - const l = lasso.value - return { - left: Math.min(l.x1, l.x2) + 'px', top: Math.min(l.y1, l.y2) + 'px', - width: Math.abs(l.x2 - l.x1) + 'px', height: Math.abs(l.y2 - l.y1) + 'px', - } - }) - - function startLasso (e) { - if (e.target.closest('.sb-block, .sb-chip, .sb-res-cell, .sb-travel-trail, button, input, select, a')) return - if (e.button !== 0) return - e.preventDefault() - if (!e.ctrlKey && !e.metaKey) { - if (selectedJob.value || multiSelect.value.length) { - selectedJob.value = null; multiSelect.value = [] - } - } - const rect = boardScroll.value.getBoundingClientRect() - const x = e.clientX - rect.left + boardScroll.value.scrollLeft - const y = e.clientY - rect.top + boardScroll.value.scrollTop - lasso.value = { x1: x, y1: y, x2: x, y2: y } - } - - function moveLasso (e) { - if (!lasso.value) return - e.preventDefault() - const rect = boardScroll.value.getBoundingClientRect() - lasso.value.x2 = e.clientX - rect.left + boardScroll.value.scrollLeft - lasso.value.y2 = e.clientY - rect.top + boardScroll.value.scrollTop - } - - function endLasso () { - if (!lasso.value) return - const l = lasso.value - const w = Math.abs(l.x2 - l.x1), h = Math.abs(l.y2 - l.y1) - if (w > 10 && h > 10) { - const boardRect = boardScroll.value.getBoundingClientRect() - const lassoLeft = Math.min(l.x1, l.x2) - boardScroll.value.scrollLeft + boardRect.left - const lassoTop = Math.min(l.y1, l.y2) - boardScroll.value.scrollTop + boardRect.top - const lassoRight = lassoLeft + w, lassoBottom = lassoTop + h - const blocks = boardScroll.value.querySelectorAll('.sb-block[data-job-id], .sb-chip') - const selected = [] - blocks.forEach(el => { - const r = el.getBoundingClientRect() - if (r.right > lassoLeft && r.left < lassoRight && r.bottom > lassoTop && r.top < lassoBottom) { - const jobId = el.dataset?.jobId - if (jobId) { - const job = store.jobs.find(j => j.id === jobId) - if (job) selected.push({ job, techId: job.assignedTech, isAssist: false, assistTechId: null }) - } - } - }) - if (selected.length) { - multiSelect.value = selected - if (selected.length === 1) selectedJob.value = selected[0] - } - } - lasso.value = null - } - - // ── Hover linking helpers ───────────────────────────────────────────────────── - function techHasLinkedJob (tech) { - const hId = hoveredJobId.value, sId = selectedJob.value?.job?.id - if (hId && (tech.assistJobs || []).some(j => j.id === hId)) return true - if (hId && tech.queue.some(j => j.id === hId)) return true - if (sId && !selectedJob.value?.isAssist && (tech.assistJobs || []).some(j => j.id === sId)) return true - if (sId && selectedJob.value?.isAssist && tech.queue.some(j => j.id === sId)) return true - return false - } - - function techIsHovered (tech) { - const hId = hoveredJobId.value - if (!hId) return false - const job = tech.queue.find(j => j.id === hId) - return job && job.assistants?.length > 0 - } - - return { - hoveredJobId, selectedJob, multiSelect, - selectJob, isJobMultiSelected, batchUnassign, batchMoveTo, - lasso, boardScroll, lassoStyle, startLasso, moveLasso, endLasso, - techHasLinkedJob, techIsHovered, - } -} diff --git a/apps/dispatch/src/composables/useUndo.js b/apps/dispatch/src/composables/useUndo.js deleted file mode 100644 index 24de03b..0000000 --- a/apps/dispatch/src/composables/useUndo.js +++ /dev/null @@ -1,78 +0,0 @@ -// ── Undo stack composable ──────────────────────────────────────────────────── -import { ref, nextTick } from 'vue' -import { updateJob } from 'src/api/dispatch' -import { serializeAssistants } from './useHelpers' - -export function useUndo (store, invalidateRoutes) { - const undoStack = ref([]) - - function pushUndo (action) { - undoStack.value.push(action) - if (undoStack.value.length > 30) undoStack.value.shift() - } - - // Restore a single job to its previous state (unassign from current tech, re-assign if it had one) - function _restoreJob (prev) { - const job = store.jobs.find(j => j.id === prev.jobId) - if (!job) return - // Remove from all tech queues first - store.technicians.forEach(t => { t.queue = t.queue.filter(q => q.id !== prev.jobId) }) - if (prev.techId) { - // Was assigned before — re-assign - store.assignJobToTech(prev.jobId, prev.techId, prev.routeOrder, prev.scheduledDate) - } else { - // Was unassigned before — just mark as open - job.assignedTech = null - job.status = 'open' - job.scheduledDate = prev.scheduledDate || null - updateJob(job.name || job.id, { assigned_tech: null, status: 'open', scheduled_date: prev.scheduledDate || '' }).catch(() => {}) - } - if (prev.assistants?.length) { - job.assistants = prev.assistants - updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }).catch(() => {}) - } - } - - function performUndo () { - const action = undoStack.value.pop() - if (!action) return - - if (action.type === 'removeAssistant') { - store.addAssistant(action.jobId, action.techId) - nextTick(() => { - const job = store.jobs.find(j => j.id === action.jobId) - const a = job?.assistants.find(x => x.techId === action.techId) - if (a) { a.duration = action.duration; a.note = action.note } - updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }).catch(() => {}) - }) - - } else if (action.type === 'optimizeRoute') { - const tech = store.technicians.find(t => t.id === action.techId) - if (tech) { - tech.queue = action.prevQueue - action.prevQueue.forEach((j, i) => { j.routeOrder = i }) - } - - } else if (action.type === 'autoDistribute') { - action.assignments.forEach(a => _restoreJob(a)) - if (action.prevQueues) { - store.technicians.forEach(t => { - if (action.prevQueues[t.id]) t.queue = action.prevQueues[t.id] - }) - } - - } else if (action.type === 'batchAssign') { - // Undo a multi-select drag — restore each job to previous state - action.assignments.forEach(a => _restoreJob(a)) - - } else if (action.type === 'unassignJob') { - _restoreJob(action) - } - - // Rebuild assistJobs on all techs - store.technicians.forEach(t => { t.assistJobs = store.jobs.filter(j => j.assistants.some(a => a.techId === t.id)) }) - invalidateRoutes() - } - - return { undoStack, pushUndo, performUndo } -} diff --git a/apps/dispatch/src/config/erpnext.js b/apps/dispatch/src/config/erpnext.js deleted file mode 100644 index 0323967..0000000 --- a/apps/dispatch/src/config/erpnext.js +++ /dev/null @@ -1,26 +0,0 @@ -// ── ERPNext connection config ──────────────────────────────────────────────── -// To host the app separately from ERPNext (e.g. Nginx, Vercel): -// - Set BASE_URL to 'https://your-erpnext.example.com' -// - Add CORS + session/JWT config on the ERPNext side -// - Update api/auth.js if switching from session cookie to JWT -// For same-origin (ERPNext serves the app): keep BASE_URL as empty string. - -// In production, /api/ is proxied to ERPNext via nginx (same-origin, no CORS) -// In dev (localhost), calls go directly to ERPNext -export const BASE_URL = window.location.hostname === 'localhost' ? 'https://erp.gigafibre.ca' : '' - -// Mapbox public token — safe to expose (scope-limited in Mapbox dashboard) -export const MAPBOX_TOKEN = 'pk.eyJ1IjoidGFyZ29pbnRlcm5ldCIsImEiOiJjbW13Z3lwMXAwdGt1MnVvamsxNWkybzFkIn0.rdYB17XUdfn96czdnnJ6eg' - -export const TECH_COLORS = [ - '#6366f1', // Indigo - '#10b981', // Emerald - '#f59e0b', // Amber - '#8b5cf6', // Violet - '#06b6d4', // Cyan - '#f43f5e', // Rose - '#f97316', // Orange - '#14b8a6', // Teal - '#d946ef', // Fuchsia - '#3b82f6', // Blue -] diff --git a/apps/dispatch/src/css/app.scss b/apps/dispatch/src/css/app.scss deleted file mode 100644 index c497e53..0000000 --- a/apps/dispatch/src/css/app.scss +++ /dev/null @@ -1,41 +0,0 @@ -// ── Global CSS variables ──────────────────────────────────────────────────── -// Shared between DispatchPage and MobilePage. -// To add a new theme: duplicate the :root block with a body class selector. - -:root { - // Dark theme (default for dispatch desktop) - --bg: #0b0f1a; - --sidebar-bg: rgba(15, 23, 42, 0.9); - --card-bg: rgba(30, 41, 59, 0.5); - --card-hover: rgba(51, 65, 85, 0.6); - --border: rgba(255, 255, 255, 0.08); - --border-accent: rgba(99, 102, 241, 0.3); - --text-primary: #f8fafc; - --text-secondary: #94a3b8; - --accent: #6366f1; - --accent-glow: rgba(99, 102, 241, 0.3); - --green: #10b981; - --green-glow: rgba(16, 185, 129, 0.2); - --orange: #f59e0b; - --red: #f43f5e; - --card-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); -} - -body.light-mode { - --bg: #ffffff; - --sidebar-bg: #ffffff; - --card-bg: #ffffff; - --card-hover: #f1f5f9; - --border: #e2e8f0; - --border-accent: #cbd5e1; - --text-primary: #0f172a; - --text-secondary: #475569; - --accent: #4f46e5; - --accent-glow: rgba(79, 70, 229, 0.1); - --card-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -*, *::before, *::after { box-sizing: border-box; } - -// Quasar resets some of these — keep them consistent -html, body { height: 100%; } diff --git a/apps/dispatch/src/modules/dispatch/components/BottomPanel.vue b/apps/dispatch/src/modules/dispatch/components/BottomPanel.vue deleted file mode 100644 index 0bc9b42..0000000 --- a/apps/dispatch/src/modules/dispatch/components/BottomPanel.vue +++ /dev/null @@ -1,194 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/JobEditModal.vue b/apps/dispatch/src/modules/dispatch/components/JobEditModal.vue deleted file mode 100644 index df5db39..0000000 --- a/apps/dispatch/src/modules/dispatch/components/JobEditModal.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/MapPanel.vue b/apps/dispatch/src/modules/dispatch/components/MapPanel.vue deleted file mode 100644 index c59f032..0000000 --- a/apps/dispatch/src/modules/dispatch/components/MapPanel.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/MonthCalendar.vue b/apps/dispatch/src/modules/dispatch/components/MonthCalendar.vue deleted file mode 100644 index 19b50d9..0000000 --- a/apps/dispatch/src/modules/dispatch/components/MonthCalendar.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/RightPanel.vue b/apps/dispatch/src/modules/dispatch/components/RightPanel.vue deleted file mode 100644 index 8427113..0000000 --- a/apps/dispatch/src/modules/dispatch/components/RightPanel.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/TimelineRow.vue b/apps/dispatch/src/modules/dispatch/components/TimelineRow.vue deleted file mode 100644 index 3f401bc..0000000 --- a/apps/dispatch/src/modules/dispatch/components/TimelineRow.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/WeekCalendar.vue b/apps/dispatch/src/modules/dispatch/components/WeekCalendar.vue deleted file mode 100644 index 0bb3bb6..0000000 --- a/apps/dispatch/src/modules/dispatch/components/WeekCalendar.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/apps/dispatch/src/modules/dispatch/components/WoCreateModal.vue b/apps/dispatch/src/modules/dispatch/components/WoCreateModal.vue deleted file mode 100644 index 16bd727..0000000 --- a/apps/dispatch/src/modules/dispatch/components/WoCreateModal.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - diff --git a/apps/dispatch/src/pages/AdminPage.vue b/apps/dispatch/src/pages/AdminPage.vue deleted file mode 100644 index 8552066..0000000 --- a/apps/dispatch/src/pages/AdminPage.vue +++ /dev/null @@ -1,557 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/pages/BookingPage.vue b/apps/dispatch/src/pages/BookingPage.vue deleted file mode 100644 index 20433e3..0000000 --- a/apps/dispatch/src/pages/BookingPage.vue +++ /dev/null @@ -1,583 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/pages/ContractorPage.vue b/apps/dispatch/src/pages/ContractorPage.vue deleted file mode 100644 index 9991e14..0000000 --- a/apps/dispatch/src/pages/ContractorPage.vue +++ /dev/null @@ -1,716 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/pages/DispatchPage.vue b/apps/dispatch/src/pages/DispatchPage.vue deleted file mode 100644 index 4547f03..0000000 --- a/apps/dispatch/src/pages/DispatchPage.vue +++ /dev/null @@ -1,1959 +0,0 @@ - - - - - - diff --git a/apps/dispatch/src/pages/DispatchV2Page.vue b/apps/dispatch/src/pages/DispatchV2Page.vue deleted file mode 100644 index ddf1d26..0000000 --- a/apps/dispatch/src/pages/DispatchV2Page.vue +++ /dev/null @@ -1,1626 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/pages/MobilePage.vue b/apps/dispatch/src/pages/MobilePage.vue deleted file mode 100644 index 1ad6fc7..0000000 --- a/apps/dispatch/src/pages/MobilePage.vue +++ /dev/null @@ -1,700 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/pages/TechBidPage.vue b/apps/dispatch/src/pages/TechBidPage.vue deleted file mode 100644 index f47ef8c..0000000 --- a/apps/dispatch/src/pages/TechBidPage.vue +++ /dev/null @@ -1,399 +0,0 @@ - - - - - diff --git a/apps/dispatch/src/router/index.js b/apps/dispatch/src/router/index.js deleted file mode 100644 index 7f5283d..0000000 --- a/apps/dispatch/src/router/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import { route } from 'quasar/wrappers' -import { createRouter, createWebHashHistory } from 'vue-router' - -// Routes — add pages here; no change needed in stores or API -const routes = [ - { path: '/', component: () => import('pages/DispatchV2Page.vue') }, - { path: '/mobile', component: () => import('pages/MobilePage.vue') }, - { path: '/admin', component: () => import('pages/AdminPage.vue') }, -] - -export default route(function () { - return createRouter({ - history: createWebHashHistory(process.env.VUE_ROUTER_BASE), - routes, - }) -}) diff --git a/apps/dispatch/src/stores/auth.js b/apps/dispatch/src/stores/auth.js deleted file mode 100644 index 1a39755..0000000 --- a/apps/dispatch/src/stores/auth.js +++ /dev/null @@ -1,51 +0,0 @@ -// ── Auth store — Authentik forwardAuth ────────────────────────────────────── -// Authentik handles login at the Traefik level. If the user reaches the app, -// they are already authenticated. We fetch their identity from the /api/ proxy -// which forwards Authentik headers to ERPNext. -// ERPNext API calls use a service token (not user session). -// ───────────────────────────────────────────────────────────────────────────── -import { defineStore } from 'pinia' -import { ref } from 'vue' -import { BASE_URL } from 'src/config/erpnext' - -// Service token for ERPNext API — all dispatch API calls use this -const ERP_SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || window.__ERP_TOKEN__ || '' - -export const useAuthStore = defineStore('auth', () => { - const user = ref(null) - const loading = ref(true) - const error = ref('') - - async function checkSession () { - loading.value = true - try { - // Fetch user identity — the /api/ proxy passes Authentik headers to ERPNext - // We use the service token to query who we are - const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { - headers: { Authorization: 'token ' + ERP_SERVICE_TOKEN }, - }) - if (res.ok) { - const data = await res.json() - // For now, use the service account identity - // The actual Authentik user email is in the response headers (X-authentik-email) - // but those are only available at the Traefik level - user.value = data.message || 'authenticated' - } else { - user.value = 'authenticated' // Authentik guarantees auth, ERPNext may not know the user - } - } catch { - user.value = 'authenticated' // If ERPNext is down, user is still authenticated via Authentik - } finally { - loading.value = false - } - } - - async function doLogout () { - // Redirect to Authentik logout - window.location.href = 'https://auth.targo.ca/application/o/gigafibre-dispatch/end-session/' - } - - return { user, loading, error, checkSession, doLogin: checkSession, doLogout } -}) - -export function getServiceToken () { return ERP_SERVICE_TOKEN } diff --git a/apps/dispatch/src/stores/dispatch.js b/apps/dispatch/src/stores/dispatch.js deleted file mode 100644 index 27670fd..0000000 --- a/apps/dispatch/src/stores/dispatch.js +++ /dev/null @@ -1,417 +0,0 @@ -// ── Dispatch store ─────────────────────────────────────────────────────────── -// Shared state for both MobilePage and DispatchPage. -// All ERPNext calls go through api/dispatch.js — not here. -// ───────────────────────────────────────────────────────────────────────────── -import { defineStore } from 'pinia' -import { ref } from 'vue' -import { fetchTechnicians, fetchJobs, updateJob, createJob as apiCreateJob, fetchTags, createTech as apiCreateTech, deleteTech as apiDeleteTech } from 'src/api/dispatch' -import { fetchDevices, fetchPositions, createTraccarSession } from 'src/api/traccar' -import { TECH_COLORS } from 'src/config/erpnext' -import { serializeAssistants } from 'src/composables/useHelpers' - -// Module-level GPS guards — survive store re-creation and component remount -let __gpsStarted = false -let __gpsInterval = null -let __gpsPolling = false - -export const useDispatchStore = defineStore('dispatch', () => { - const technicians = ref([]) - const jobs = ref([]) - const allTags = ref([]) // { name, label, color, category } - const loading = ref(false) - const erpStatus = ref('pending') // 'pending' | 'ok' | 'error' | 'session_expired' - - // ── Data transformers ──────────────────────────────────────────────────── - - function _mapJob (j) { - return { - id: j.ticket_id || j.name, - name: j.name, // ERPNext docname (used for PUT calls) - subject: j.subject || 'Job sans titre', - address: j.address || 'Adresse inconnue', - coords: [j.longitude || 0, j.latitude || 0], - priority: j.priority || 'low', - duration: j.duration_h || 1, - status: j.status || 'open', - assignedTech: j.assigned_tech || null, - routeOrder: j.route_order || 0, - legDist: j.leg_distance || null, - legDur: j.leg_duration || null, - scheduledDate: j.scheduled_date || null, - endDate: j.end_date || null, - startTime: j.start_time || null, - assistants: (j.assistants || []).map(a => ({ techId: a.tech_id, techName: a.tech_name, duration: a.duration_h || 0, note: a.note || '', pinned: !!a.pinned })), - tags: (j.tags || []).map(t => t.tag), - } - } - - function _mapTech (t, idx) { - return { - id: t.technician_id || t.name, - name: t.name, // ERPNext docname - fullName: t.full_name || t.name, - status: t.status || '', - user: t.user || null, - colorIdx: idx % TECH_COLORS.length, - coords: [t.longitude || -73.5673, t.latitude || 45.5017], - gpsCoords: null, // live GPS from Traccar (updated by polling) - gpsSpeed: 0, - gpsTime: null, - gpsOnline: false, - traccarDeviceId: t.traccar_device_id || null, - phone: t.phone || '', - email: t.email || '', - queue: [], // filled in loadAll() - tags: (t.tags || []).map(tg => tg.tag), - } - } - - // ── Loaders ────────────────────────────────────────────────────────────── - - async function loadAll () { - loading.value = true - erpStatus.value = 'pending' - try { - const [rawTechs, rawJobs, rawTags] = await Promise.all([ - fetchTechnicians(), - fetchJobs(), - fetchTags(), - ]) - allTags.value = rawTags - technicians.value = rawTechs.map(_mapTech) - jobs.value = rawJobs.map(_mapJob) - // Build each tech's ordered queue (primary + assistant jobs) - technicians.value.forEach(tech => { - tech.queue = jobs.value - .filter(j => j.assignedTech === tech.id) - .sort((a, b) => a.routeOrder - b.routeOrder) - tech.assistJobs = jobs.value - .filter(j => j.assistants.some(a => a.techId === tech.id)) - }) - erpStatus.value = 'ok' - } catch (e) { - erpStatus.value = e.message?.includes('session') ? 'session_expired' : 'error' - console.error('loadAll error:', e) - } finally { - loading.value = false - } - } - - // Load jobs assigned to one tech — used by MobilePage - async function loadJobsForTech (techId) { - loading.value = true - try { - const raw = await fetchJobs([['assigned_tech', '=', techId]]) - jobs.value = raw.map(_mapJob) - } finally { - loading.value = false - } - } - - // ── Mutations (also syncs to ERPNext) ──────────────────────────────────── - - async function setJobStatus (jobId, status) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - job.status = status - await updateJob(job.id, { status }) - } - - async function assignJobToTech (jobId, techId, routeOrder, scheduledDate) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - // Remove from old tech queue - technicians.value.forEach(t => { - t.queue = t.queue.filter(q => q.id !== jobId) - }) - // Add to new tech queue - const tech = technicians.value.find(t => t.id === techId) - if (tech) { - job.assignedTech = techId - job.routeOrder = routeOrder - job.status = 'assigned' - if (scheduledDate !== undefined) job.scheduledDate = scheduledDate - tech.queue.splice(routeOrder, 0, job) - // Re-number route_order - tech.queue.forEach((q, i) => { q.routeOrder = i }) - } - const payload = { - assigned_tech: techId, - route_order: routeOrder, - status: 'assigned', - } - if (scheduledDate !== undefined) payload.scheduled_date = scheduledDate || '' - await updateJob(job.id, payload) - } - - async function unassignJob (jobId) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - technicians.value.forEach(t => { t.queue = t.queue.filter(q => q.id !== jobId) }) - job.assignedTech = null - job.status = 'open' - try { await updateJob(job.name || job.id, { assigned_tech: null, status: 'open' }) } catch (_) {} - } - - async function createJob (fields) { - // fields: { subject, address, duration_h, priority, assigned_tech?, scheduled_date?, start_time? } - const localId = 'WO-' + Date.now().toString(36).toUpperCase() - const job = _mapJob({ - ticket_id: localId, name: localId, - subject: fields.subject || 'Nouveau travail', - address: fields.address || '', - longitude: fields.longitude || 0, - latitude: fields.latitude || 0, - duration_h: parseFloat(fields.duration_h) || 1, - priority: fields.priority || 'low', - status: fields.assigned_tech ? 'assigned' : 'open', - assigned_tech: fields.assigned_tech || null, - scheduled_date: fields.scheduled_date || null, - start_time: fields.start_time || null, - route_order: 0, - }) - jobs.value.push(job) - if (fields.assigned_tech) { - const tech = technicians.value.find(t => t.id === fields.assigned_tech) - if (tech) { job.routeOrder = tech.queue.length; tech.queue.push(job) } - } - try { - const created = await apiCreateJob({ - subject: job.subject, - address: job.address, - longitude: job.coords?.[0] || '', - latitude: job.coords?.[1] || '', - duration_h: job.duration, - priority: job.priority, - status: job.status, - assigned_tech: job.assignedTech || '', - scheduled_date: job.scheduledDate || '', - start_time: job.startTime || '', - }) - if (created?.name) { job.id = created.name; job.name = created.name } - } catch (_) {} - return job - } - - async function setJobSchedule (jobId, scheduledDate, startTime) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - job.scheduledDate = scheduledDate || null - job.startTime = startTime !== undefined ? startTime : job.startTime - const payload = { scheduled_date: job.scheduledDate || '' } - if (startTime !== undefined) payload.start_time = startTime || '' - try { await updateJob(job.name || job.id, payload) } catch (_) {} - } - - async function updateJobCoords (jobId, lng, lat) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - job.coords = [lng, lat] - try { await updateJob(job.name || job.id, { longitude: lng, latitude: lat }) } catch (_) {} - } - - async function addAssistant (jobId, techId) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - if (job.assignedTech === techId) return // already lead - if (job.assistants.some(a => a.techId === techId)) return // already assistant - const tech = technicians.value.find(t => t.id === techId) - const entry = { techId, techName: tech?.fullName || techId, duration: job.duration, note: '', pinned: false } - job.assistants = [...job.assistants, entry] - if (tech) tech.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === tech.id)) - try { - await updateJob(job.name || job.id, { - assistants: serializeAssistants(job.assistants), - }) - } catch (_) {} - } - - async function removeAssistant (jobId, techId) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - job.assistants = job.assistants.filter(a => a.techId !== techId) - const tech = technicians.value.find(t => t.id === techId) - if (tech) tech.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === tech.id)) - try { - await updateJob(job.name || job.id, { - assistants: serializeAssistants(job.assistants), - }) - } catch (_) {} - } - - async function reorderTechQueue (techId, fromIdx, toIdx) { - const tech = technicians.value.find(t => t.id === techId) - if (!tech) return - const [moved] = tech.queue.splice(fromIdx, 1) - tech.queue.splice(toIdx, 0, moved) - tech.queue.forEach((q, i) => { q.routeOrder = i }) - // Sync all reordered jobs - await Promise.all( - tech.queue.map((q, i) => updateJob(q.id, { route_order: i })), - ) - } - - // ── Smart assign (removes circular assistant deps) ────────────────────── - function smartAssign (jobId, newTechId, dateStr) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - if (job.assistants.some(a => a.techId === newTechId)) { - job.assistants = job.assistants.filter(a => a.techId !== newTechId) - updateJob(job.name || job.id, { assistants: serializeAssistants(job.assistants) }).catch(() => {}) - } - assignJobToTech(jobId, newTechId, technicians.value.find(t => t.id === newTechId)?.queue.length || 0, dateStr) - _rebuildAssistJobs() - } - - // ── Full unassign (clears assistants + unassigns) ────────────────────── - function fullUnassign (jobId) { - const job = jobs.value.find(j => j.id === jobId) - if (!job) return - if (job.assistants.length) { job.assistants = []; updateJob(job.name || job.id, { assistants: [] }).catch(() => {}) } - unassignJob(jobId) - _rebuildAssistJobs() - } - - // Rebuild all tech.assistJobs references - function _rebuildAssistJobs () { - technicians.value.forEach(t => { t.assistJobs = jobs.value.filter(j => j.assistants.some(a => a.techId === t.id)) }) - } - - // ── Traccar GPS — Hybrid: REST initial + WebSocket real-time ───────────────── - const traccarDevices = ref([]) - const _techsByDevice = {} // deviceId (number) → tech object - - function _buildTechDeviceMap () { - Object.keys(_techsByDevice).forEach(k => delete _techsByDevice[k]) - technicians.value.forEach(t => { - if (!t.traccarDeviceId) return - const dev = traccarDevices.value.find(d => d.id === parseInt(t.traccarDeviceId) || d.uniqueId === t.traccarDeviceId) - if (dev) _techsByDevice[dev.id] = t - }) - } - - function _applyPositions (positions) { - positions.forEach(p => { - const tech = _techsByDevice[p.deviceId] - if (!tech || !p.latitude || !p.longitude) return - const cur = tech.gpsCoords - if (!cur || Math.abs(cur[0] - p.longitude) > 0.00001 || Math.abs(cur[1] - p.latitude) > 0.00001) { - tech.gpsCoords = [p.longitude, p.latitude] - } - tech.gpsSpeed = p.speed || 0 - tech.gpsTime = p.fixTime - tech.gpsOnline = true - }) - } - - // One-shot REST fetch (manual refresh button + initial load) - async function pollGps () { - if (__gpsPolling) return - __gpsPolling = true - try { - if (!traccarDevices.value.length) traccarDevices.value = await fetchDevices() - _buildTechDeviceMap() - const deviceIds = Object.keys(_techsByDevice).map(Number) - if (!deviceIds.length) return - const positions = await fetchPositions(deviceIds) - _applyPositions(positions) - Object.values(_techsByDevice).forEach(t => { - if (!positions.find(p => _techsByDevice[p.deviceId] === t)) t.gpsOnline = false - }) - } catch (e) { console.warn('[GPS] Poll error:', e.message) } - finally { __gpsPolling = false } - } - - // WebSocket connection with auto-reconnect - let __ws = null - let __wsBackoff = 1000 - - function _connectWs () { - if (__ws) return - const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:' - const url = proto + '//' + window.location.host + '/traccar/api/socket' - try { __ws = new WebSocket(url) } catch (e) { console.warn('[GPS] WS error:', e); return } - __ws.onopen = () => { - __wsBackoff = 1000 - // WS connected — stop fallback polling - if (__gpsInterval) { clearInterval(__gpsInterval); __gpsInterval = null } - console.log('[GPS] WebSocket connected — real-time updates active') - } - __ws.onmessage = (e) => { - try { - const data = JSON.parse(e.data) - if (data.positions?.length) { - _buildTechDeviceMap() // refresh map in case techs changed - _applyPositions(data.positions) - } - } catch {} - } - __ws.onerror = () => {} - __ws.onclose = () => { - __ws = null - if (!__gpsStarted) return - // Start fallback polling while WS is down - if (!__gpsInterval) { - __gpsInterval = setInterval(pollGps, 30000) - console.log('[GPS] WS closed — fallback to 30s polling') - } - setTimeout(_connectWs, __wsBackoff) - __wsBackoff = Math.min(__wsBackoff * 2, 60000) - } - } - - async function startGpsTracking () { - if (__gpsStarted) return - __gpsStarted = true - // 1. Load devices + initial REST fetch (all last-known positions) - await pollGps() - console.log('[GPS] Initial positions loaded via REST') - // 2. Create session cookie for WebSocket auth, then connect - const sessionOk = await createTraccarSession() - if (sessionOk) { - _connectWs() - } else { - // Session failed — fall back to polling - __gpsInterval = setInterval(pollGps, 30000) - console.log('[GPS] Session failed — fallback to 30s polling') - } - } - - function stopGpsTracking () { - if (__gpsInterval) { clearInterval(__gpsInterval); __gpsInterval = null } - if (__ws) { const ws = __ws; __ws = null; ws.onclose = null; ws.close() } - } - - const startGpsPolling = startGpsTracking - const stopGpsPolling = stopGpsTracking - - // ── Create / Delete technician ───────────────────────────────────────────── - async function createTechnician (fields) { - // Auto-generate technician_id: TECH-N+1 - const maxNum = technicians.value.reduce((max, t) => { - const m = (t.id || '').match(/TECH-(\d+)/) - return m ? Math.max(max, parseInt(m[1])) : max - }, 0) - fields.technician_id = 'TECH-' + (maxNum + 1) - const doc = await apiCreateTech(fields) - const tech = _mapTech(doc, technicians.value.length) - technicians.value.push(tech) - return tech - } - - async function deleteTechnician (techId) { - const tech = technicians.value.find(t => t.id === techId) - if (!tech) return - await apiDeleteTech(tech.name) - technicians.value = technicians.value.filter(t => t.id !== techId) - } - - return { - technicians, jobs, allTags, loading, erpStatus, traccarDevices, - loadAll, loadJobsForTech, - setJobStatus, assignJobToTech, unassignJob, createJob, reorderTechQueue, updateJobCoords, setJobSchedule, addAssistant, removeAssistant, - smartAssign, fullUnassign, - pollGps, startGpsTracking, stopGpsTracking, startGpsPolling, stopGpsPolling, - createTechnician, deleteTechnician, - } -}) diff --git a/apps/field/infra/docker-compose.yaml b/apps/field/infra/docker-compose.yaml index e211597..4469255 100644 --- a/apps/field/infra/docker-compose.yaml +++ b/apps/field/infra/docker-compose.yaml @@ -15,7 +15,7 @@ services: - "traefik.enable=true" - "traefik.http.routers.field.rule=Host(`erp.gigafibre.ca`) && PathPrefix(`/field`)" - "traefik.http.routers.field.entrypoints=web,websecure" - - "traefik.http.routers.field.middlewares=authentik-client@file,field-strip@docker" + - "traefik.http.routers.field.middlewares=authentik@file,field-strip@docker" - "traefik.http.routers.field.service=field" - "traefik.http.routers.field.tls.certresolver=letsencrypt" - "traefik.http.routers.field.priority=200" diff --git a/apps/field/infra/nginx.conf b/apps/field/infra/nginx.conf index 404db16..63398f7 100644 --- a/apps/field/infra/nginx.conf +++ b/apps/field/infra/nginx.conf @@ -16,15 +16,30 @@ server { proxy_set_header X-Forwarded-Proto https; } - # Ollama Vision API proxy — for bill/invoice OCR + # Ollama Vision API proxy — for bill/invoice OCR (legacy, optional) location /ollama/ { - proxy_pass http://ollama:11434/; + resolver 127.0.0.11 valid=10s; + set $ollama_upstream http://ollama:11434; + proxy_pass $ollama_upstream/; proxy_set_header Host $host; proxy_read_timeout 300s; proxy_send_timeout 300s; client_max_body_size 20m; } + # Targo Hub API proxy — vision, devices, etc. + location /hub/ { + resolver 127.0.0.11 valid=10s; + set $hub_upstream http://targo-hub:3300; + proxy_pass $hub_upstream/; + proxy_set_header Host $host; + proxy_set_header X-Authentik-Email $http_x_authentik_email; + proxy_set_header X-Authentik-Username $http_x_authentik_username; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 60s; + client_max_body_size 20m; + } + # SPA fallback location / { try_files $uri $uri/ /index.html; diff --git a/apps/field/quasar.config.js b/apps/field/quasar.config.js index bf64fbd..857b1f3 100644 --- a/apps/field/quasar.config.js +++ b/apps/field/quasar.config.js @@ -33,7 +33,9 @@ module.exports = configure(function () { }, framework: { - config: {}, + config: { + notify: { position: 'top', timeout: 2500 }, + }, plugins: ['Notify', 'Loading', 'LocalStorage', 'Dialog', 'BottomSheet'], }, diff --git a/apps/field/src/api/auth.js b/apps/field/src/api/auth.js index 92f3781..7346252 100644 --- a/apps/field/src/api/auth.js +++ b/apps/field/src/api/auth.js @@ -14,9 +14,6 @@ export function authFetch (url, opts = {}) { opts.headers = { ...opts.headers } } opts.redirect = 'manual' - if (opts.method && opts.method !== 'GET') { - opts.credentials = 'omit' - } return fetch(url, opts).then(res => { if (res.type === 'opaqueredirect' || res.status === 302 || res.status === 401) { window.location.reload() diff --git a/apps/field/src/api/ocr.js b/apps/field/src/api/ocr.js index 896ba96..3f66110 100644 --- a/apps/field/src/api/ocr.js +++ b/apps/field/src/api/ocr.js @@ -1,6 +1,7 @@ import { authFetch } from './auth' const OLLAMA_URL = '/ollama/api/generate' +const HUB_VISION_URL = 'https://msg.gigafibre.ca/vision/barcodes' const OCR_PROMPT = `You are an invoice/bill OCR assistant. Extract the following fields from this image of a bill or invoice. Return ONLY valid JSON, no markdown, no explanation. @@ -66,6 +67,28 @@ export async function ocrBill (base64Image) { } } +/** + * Send image to Gemini Vision (via targo-hub) for barcode/serial extraction. + * @param {string} base64Image — base64 or data URI + * @returns {{ barcodes: string[] }} + */ +export async function scanBarcodes (base64Image) { + // Direct call to targo-hub (cross-origin, no auth needed) + const res = await fetch(HUB_VISION_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ image: base64Image }), + }) + + if (!res.ok) { + const text = await res.text() + throw new Error('Vision scan failed: ' + (text || res.status)) + } + + const data = await res.json() + return { barcodes: data.barcodes || [] } +} + /** * Check if Ollama is running and the vision model is available. */ diff --git a/apps/field/src/composables/useScanner.js b/apps/field/src/composables/useScanner.js index 98bac6c..a3e3872 100644 --- a/apps/field/src/composables/useScanner.js +++ b/apps/field/src/composables/useScanner.js @@ -1,111 +1,95 @@ import { ref } from 'vue' +import { scanBarcodes } from 'src/api/ocr' /** - * Multi-barcode scanner from camera photo. - * Takes a picture, splits into horizontal strips, scans each for barcodes. - * Also supports live scanning mode. + * Barcode scanner using device camera photo capture + Gemini Vision AI. + * + * Strategy: Use which triggers + * the native camera app — this gives proper autofocus, tap-to-focus, + * and high-res photos. Then send to Gemini Vision for barcode extraction. + * + * Also keeps a thumbnail of each captured photo for reference. */ export function useScanner () { const barcodes = ref([]) // Array of { value, region } — max 3 - const scanning = ref(false) + const scanning = ref(false) // true while Gemini is processing const error = ref(null) - let _scanner = null + const lastPhoto = ref(null) // data URI of last captured photo (thumbnail) + const photos = ref([]) // all captured photo thumbnails - // Scan a photo for up to 3 barcodes by splitting into strips - async function scanPhoto (file) { + /** + * Process a photo file from camera input. + * Resizes for AI, keeps thumbnail, sends to Gemini. + * @param {File} file - image file from camera + * @returns {string[]} newly found barcode values + */ + async function processPhoto (file) { + if (!file) return [] error.value = null - barcodes.value = [] scanning.value = true + const found = [] + try { - const { Html5Qrcode } = await import('html5-qrcode') - const scanner = new Html5Qrcode('scanner-scratch', { verbose: false }) + // Create thumbnail for display (small) + const thumbUrl = await resizeImage(file, 400) + lastPhoto.value = thumbUrl + photos.value.push({ url: thumbUrl, ts: Date.now(), codes: [] }) - // Load image as bitmap - const img = await createImageBitmap(file) - const { width, height } = img + // Create optimized image for AI — keep high res for text readability + const aiImage = await resizeImage(file, 1600, 0.92) - // Split into 3 horizontal strips and scan each - const strips = [ - { y: 0, h: Math.floor(height / 3), label: 'haut' }, - { y: Math.floor(height / 3), h: Math.floor(height / 3), label: 'milieu' }, - { y: Math.floor(height * 2 / 3), h: height - Math.floor(height * 2 / 3), label: 'bas' }, - ] + // Send to Gemini Vision + const result = await scanBarcodes(aiImage) + const existing = new Set(barcodes.value.map(b => b.value)) - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - const found = new Set() - - // First try the full image - try { - const result = await scanner.scanFileV2(file, false) - if (result?.decodedText && !found.has(result.decodedText)) { - found.add(result.decodedText) - barcodes.value.push({ value: result.decodedText, region: 'complet' }) - } - } catch {} - - // Then try each strip for additional barcodes - for (const strip of strips) { + for (const code of (result.barcodes || [])) { if (barcodes.value.length >= 3) break - canvas.width = width - canvas.height = strip.h - ctx.drawImage(img, 0, strip.y, width, strip.h, 0, 0, width, strip.h) - - try { - const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.9)) - const stripFile = new File([blob], 'strip.jpg', { type: 'image/jpeg' }) - const result = await scanner.scanFileV2(stripFile, false) - if (result?.decodedText && !found.has(result.decodedText)) { - found.add(result.decodedText) - barcodes.value.push({ value: result.decodedText, region: strip.label }) - } - } catch {} + if (!existing.has(code)) { + existing.add(code) + barcodes.value.push({ value: code, region: 'photo' }) + found.push(code) + } } - img.close() - scanner.clear() + // Tag the photo with found codes + const lastIdx = photos.value.length - 1 + if (lastIdx >= 0) photos.value[lastIdx].codes = found - if (barcodes.value.length === 0) { - error.value = 'Aucun code-barres détecté' + if (found.length === 0) { + error.value = 'Aucun code détecté — rapprochez-vous ou améliorez la mise au point' } } catch (e) { - error.value = e.message || 'Erreur scanner' + error.value = e.message || 'Erreur' } finally { scanning.value = false } + + return found } - // Live scanning mode — continuous camera feed - async function startLive (elementId, onDecode) { - error.value = null - scanning.value = true - try { - const { Html5Qrcode } = await import('html5-qrcode') - _scanner = new Html5Qrcode(elementId, { verbose: false }) - await _scanner.start( - { facingMode: 'environment' }, - { fps: 10, qrbox: { width: 280, height: 100 } }, - (decoded) => { - if (barcodes.value.length < 3 && !barcodes.value.find(b => b.value === decoded)) { - barcodes.value.push({ value: decoded, region: 'live' }) - onDecode?.(decoded) - } + /** + * Resize an image file to a max dimension, return as base64 data URI. + */ + function resizeImage (file, maxDim, quality = 0.85) { + return new Promise((resolve, reject) => { + const img = new Image() + img.onload = () => { + let { width, height } = img + if (width > maxDim || height > maxDim) { + const ratio = Math.min(maxDim / width, maxDim / height) + width = Math.round(width * ratio) + height = Math.round(height * ratio) } - ) - } catch (e) { - error.value = e.message || 'Caméra non disponible' - scanning.value = false - } - } - - async function stopLive () { - try { - if (_scanner?.isScanning) await _scanner.stop() - _scanner?.clear() - } catch {} - _scanner = null - scanning.value = false + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + canvas.getContext('2d').drawImage(img, 0, 0, width, height) + resolve(canvas.toDataURL('image/jpeg', quality)) + } + img.onerror = reject + img.src = URL.createObjectURL(file) + }) } function removeBarcode (value) { @@ -115,7 +99,12 @@ export function useScanner () { function clearBarcodes () { barcodes.value = [] error.value = null + lastPhoto.value = null + photos.value = [] } - return { barcodes, scanning, error, scanPhoto, startLive, stopLive, removeBarcode, clearBarcodes } + return { + barcodes, scanning, error, lastPhoto, photos, + processPhoto, removeBarcode, clearBarcodes, + } } diff --git a/apps/field/src/config/erpnext.js b/apps/field/src/config/erpnext.js index ba8305e..8f19327 100644 --- a/apps/field/src/config/erpnext.js +++ b/apps/field/src/config/erpnext.js @@ -1 +1,4 @@ -export const BASE_URL = '' +// Route API calls through field-frontend nginx which injects the ERP token. +// Without this, POST/PUT/DELETE fail with 403 (CSRF) because they go directly +// to ERPNext via Traefik without the API token header. +export const BASE_URL = '/field' diff --git a/apps/field/src/layouts/FieldLayout.vue b/apps/field/src/layouts/FieldLayout.vue index 382913d..7346c74 100644 --- a/apps/field/src/layouts/FieldLayout.vue +++ b/apps/field/src/layouts/FieldLayout.vue @@ -7,7 +7,6 @@ Targo Field - @@ -15,8 +14,15 @@ - + + + +
+ + Hors ligne +
+
@@ -42,4 +48,31 @@ const offline = useOfflineStore() font-size: 11px; } } + +.offline-banner { + display: flex; + align-items: center; + justify-content: center; + background: #c62828; + color: white; + font-size: 13px; + font-weight: 500; + padding: 4px 0; +} + +.slide-down-enter-active, +.slide-down-leave-active { + transition: max-height 0.3s ease, opacity 0.3s ease; + overflow: hidden; +} +.slide-down-enter-from, +.slide-down-leave-to { + max-height: 0; + opacity: 0; +} +.slide-down-enter-to, +.slide-down-leave-from { + max-height: 30px; + opacity: 1; +} diff --git a/apps/field/src/pages/ScanPage.vue b/apps/field/src/pages/ScanPage.vue index 868374c..08932f8 100644 --- a/apps/field/src/pages/ScanPage.vue +++ b/apps/field/src/pages/ScanPage.vue @@ -1,55 +1,64 @@ + + diff --git a/apps/field/src/pages/TasksPage.vue b/apps/field/src/pages/TasksPage.vue index db6c53d..15e07c1 100644 --- a/apps/field/src/pages/TasksPage.vue +++ b/apps/field/src/pages/TasksPage.vue @@ -42,7 +42,13 @@ + @click.stop="$router.push({ name: 'scan', query: { + job: job.name, + customer: job.customer, + customer_name: job.customer_name, + location: job.service_location, + location_name: job.service_location_name, + } })" />
diff --git a/apps/ops/.quasar/client-entry.js b/apps/ops/.quasar/client-entry.js index dd88a19..de61936 100644 --- a/apps/ops/.quasar/client-entry.js +++ b/apps/ops/.quasar/client-entry.js @@ -38,8 +38,6 @@ import createQuasarApp from './app.js' import quasarUserOptions from './quasar-user-options.js' -import 'app/src-pwa/register-service-worker' - diff --git a/apps/ops/src/api/auth.js b/apps/ops/src/api/auth.js index ac885ad..aef4afc 100644 --- a/apps/ops/src/api/auth.js +++ b/apps/ops/src/api/auth.js @@ -25,6 +25,15 @@ export function authFetch (url, opts = {}) { } export async function getLoggedUser () { + // First try nginx whoami endpoint (returns Authentik email header) + try { + const res = await fetch(BASE_URL + '/auth/whoami') + if (res.ok) { + const data = await res.json() + if (data.email && data.email !== '') return data.email + } + } catch {} + // Fallback: ask ERPNext (returns API token owner, usually "Administrator") try { const headers = SERVICE_TOKEN ? { Authorization: 'token ' + SERVICE_TOKEN } : {} const res = await fetch(BASE_URL + '/api/method/frappe.auth.get_logged_user', { headers }) @@ -37,5 +46,5 @@ export async function getLoggedUser () { } export async function logout () { - window.location.href = 'https://id.gigafibre.ca/if/flow/default-invalidation-flow/' + window.location.href = 'https://auth.targo.ca/if/flow/default-invalidation-flow/' } diff --git a/apps/ops/src/api/dispatch.js b/apps/ops/src/api/dispatch.js index d5ea57c..08b27ef 100644 --- a/apps/ops/src/api/dispatch.js +++ b/apps/ops/src/api/dispatch.js @@ -21,35 +21,42 @@ async function apiPut (doctype, name, body) { body: JSON.stringify(body), }, ) - if (!res.ok) console.error(`[API] PUT ${doctype}/${name} failed:`, res.status, await res.text().catch(() => '')) - const data = await res.json() + const text = await res.text() + if (!res.ok) console.error(`[API] PUT ${doctype}/${name} failed:`, res.status, text) + let data + try { data = JSON.parse(text) } catch { throw new Error(`PUT ${doctype}/${name}: invalid JSON — ${text.slice(0, 200)}`) } if (data.exc) throw new Error(data.exc) return data } +// Fetch docs in parallel batches to avoid browser connection limits +async function batchFetchDocs (doctype, names, batchSize = 10) { + const docs = [] + for (let i = 0; i < names.length; i += batchSize) { + const batch = names.slice(i, i + batchSize) + const results = await Promise.all( + batch.map(n => apiGet(`/api/resource/${encodeURIComponent(doctype)}/${encodeURIComponent(n)}`).then(d => d.data)) + ) + docs.push(...results) + } + return docs +} + export async function fetchTechnicians () { const list = await apiGet('/api/resource/Dispatch%20Technician?fields=["name"]&limit=100') const names = (list.data || []).map(t => t.name) if (!names.length) return [] - const docs = await Promise.all( - names.map(n => apiGet(`/api/resource/Dispatch%20Technician/${encodeURIComponent(n)}`).then(d => d.data)) - ) - return docs + return batchFetchDocs('Dispatch Technician', names, 15) } // Fetch all jobs with child tables (assistants) export async function fetchJobs (filters = null) { - // Step 1: get job names from list endpoint let url = '/api/resource/Dispatch%20Job?fields=["name"]&limit=200' if (filters) url += '&filters=' + encodeURIComponent(JSON.stringify(filters)) const list = await apiGet(url) const names = (list.data || []).map(j => j.name) if (!names.length) return [] - // Step 2: fetch each doc individually (includes child tables) - const docs = await Promise.all( - names.map(n => apiGet(`/api/resource/Dispatch%20Job/${encodeURIComponent(n)}`).then(d => d.data)) - ) - return docs + return batchFetchDocs('Dispatch Job', names, 15) } export async function updateJob (name, payload) { @@ -70,6 +77,15 @@ export async function createJob (payload) { return data.data } +export async function publishJobs (jobNames) { + const results = await Promise.allSettled( + jobNames.map(name => apiPut('Dispatch Job', name, { published: 1 })) + ) + const failed = results.filter(r => r.status === 'rejected') + if (failed.length) console.warn(`[publishJobs] ${failed.length}/${jobNames.length} failed`) + return { published: jobNames.length - failed.length, failed: failed.length } +} + export async function updateTech (name, payload) { return apiPut('Dispatch Technician', name, payload) } diff --git a/apps/ops/src/api/erp.js b/apps/ops/src/api/erp.js index 2e82a58..14494c4 100644 --- a/apps/ops/src/api/erp.js +++ b/apps/ops/src/api/erp.js @@ -47,8 +47,11 @@ export async function createDoc (doctype, data) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) - if (!res.ok) throw new Error('Create failed: ' + res.status) const json = await res.json() + if (!res.ok) { + const msg = json?.exception?.split('\n')[0] || json?.message || ('Create failed: ' + res.status) + throw new Error(msg) + } return json.data } diff --git a/apps/ops/src/api/traccar.js b/apps/ops/src/api/traccar.js index a3b6017..c3e3f65 100644 --- a/apps/ops/src/api/traccar.js +++ b/apps/ops/src/api/traccar.js @@ -1,31 +1,18 @@ -// ── Traccar GPS API ────────────────────────────────────────────────────────── -// Polls Traccar for real-time device positions. -// Auth: session cookie via POST /api/session +// ── Traccar GPS API (via targo-hub proxy) ─────────────────────────────────── +// All requests go through targo-hub which holds Traccar credentials server-side. +// No credentials in the frontend. // ───────────────────────────────────────────────────────────────────────────── -// Use proxy on same origin to avoid mixed content (HTTPS → HTTP) -const TRACCAR_URL = window.location.hostname === 'localhost' - ? 'http://tracker.targointernet.com:8082' - : window.location.origin + '/traccar' -const TRACCAR_USER = 'louis@targo.ca' -const TRACCAR_PASS = 'targo2026' +const HUB_URL = window.location.hostname === 'localhost' + ? 'http://localhost:3300' + : 'https://msg.gigafibre.ca' let _devices = [] -// Use Basic auth — works through proxy without cookies -function authOpts () { - return { - headers: { - Authorization: 'Basic ' + btoa(TRACCAR_USER + ':' + TRACCAR_PASS), - Accept: 'application/json', - } - } -} - // ── Devices ────────────────────────────────────────────────────────────────── export async function fetchDevices () { try { - const res = await fetch(TRACCAR_URL + '/api/devices?all=true', authOpts()) + const res = await fetch(HUB_URL + '/traccar/devices') if (res.ok) { _devices = await res.json() return _devices @@ -34,17 +21,14 @@ export async function fetchDevices () { return _devices } -// ── Positions ──────────────────────────────────────────────────────────────── -// Traccar API only supports ONE deviceId per request — fetch in parallel +// ── Positions (batch via hub) ─────────────────────────────────────────────── export async function fetchPositions (deviceIds = null) { - if (!deviceIds || !deviceIds.length) return [] - const results = await Promise.allSettled( - deviceIds.map(id => - fetch(TRACCAR_URL + '/api/positions?deviceId=' + id, authOpts()) - .then(r => r.ok ? r.json() : []) - ) - ) - return results.flatMap(r => r.status === 'fulfilled' ? r.value : []) + if (!deviceIds?.length) return [] + try { + const res = await fetch(HUB_URL + '/traccar/positions?deviceIds=' + deviceIds.join(',')) + if (res.ok) return res.json() + } catch {} + return [] } // ── Get position for a specific device ─────────────────────────────────────── @@ -55,11 +39,9 @@ export async function fetchDevicePosition (deviceId) { // ── Get all positions mapped by deviceId ───────────────────────────────────── export async function fetchAllPositions () { - // Get devices we care about (online + offline with recent position) if (!_devices.length) await fetchDevices() const deviceIds = _devices.filter(d => d.positionId).map(d => d.id) if (!deviceIds.length) return {} - const positions = await fetchPositions(deviceIds) const map = {} positions.forEach(p => { map[p.deviceId] = p }) @@ -78,17 +60,10 @@ export function matchDeviceToTech (devices, techs) { return matched } -// ── Session (required for WebSocket auth) ──────────────────────────────────── +// ── Session (for WebSocket — now via hub SSE or polling) ───────────────────── export async function createTraccarSession () { - try { - const res = await fetch(TRACCAR_URL + '/api/session', { - method: 'POST', - credentials: 'include', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ email: TRACCAR_USER, password: TRACCAR_PASS }), - }) - return res.ok - } catch { return false } + // WebSocket not supported through hub proxy — use polling + return false } -export { TRACCAR_URL, _devices as cachedDevices } +export { _devices as cachedDevices } diff --git a/apps/ops/src/components/customer/ChatterPanel.vue b/apps/ops/src/components/customer/ChatterPanel.vue index 0028405..7dd5877 100644 --- a/apps/ops/src/components/customer/ChatterPanel.vue +++ b/apps/ops/src/components/customer/ChatterPanel.vue @@ -8,26 +8,27 @@
+ v-model:channel="composeChannel" v-model:text="composeText" + v-model:email-subject="emailSubject" v-model:sms-to="smsTo" v-model:call-to="callTo" + :customer-phone="customerPhone" :phone-options="phoneOptions" + :sending="sending" :calling="calling" :canned-responses="cannedResponses" + @send="send" @call="initiateCall" @use-canned="useCanned" @manage-canned="cannedModal = true" /> + + +
+ + {{ selectedIds.size }} sélectionné{{ selectedIds.size > 1 ? 's' : '' }} + + + Supprimer la sélection + + +
+ toggle-color="indigo-6" color="grey-2" text-color="grey-7" :options="tabOptions" />
@@ -41,12 +42,16 @@ diff --git a/apps/ops/src/composables/useAddressSearch.js b/apps/ops/src/composables/useAddressSearch.js new file mode 100644 index 0000000..a57ceb0 --- /dev/null +++ b/apps/ops/src/composables/useAddressSearch.js @@ -0,0 +1,37 @@ +import { ref } from 'vue' + +/** + * Reusable address search composable. + * Searches ERPNext `search_address` whitelisted method with debounce. + */ +export function useAddressSearch () { + const addrResults = ref([]) + const addrLoading = ref(false) + let addrDebounce = null + + function searchAddr (q) { + clearTimeout(addrDebounce) + if (!q || q.length < 3) { addrResults.value = []; return } + addrLoading.value = true + addrDebounce = setTimeout(async () => { + try { + const res = await fetch(`/api/method/search_address?q=${encodeURIComponent(q)}`, { credentials: 'include' }) + const data = await res.json() + addrResults.value = data.results || data.message?.results || [] + } catch { addrResults.value = [] } + addrLoading.value = false + }, 300) + } + + function selectAddr (addr, target) { + if (!target) { addrResults.value = []; return } + target.address = addr.address_full + if (addr.latitude) target.latitude = parseFloat(addr.latitude) + if (addr.longitude) target.longitude = parseFloat(addr.longitude) + if (addr.ville) target.ville = addr.ville + if (addr.code_postal) target.code_postal = addr.code_postal + addrResults.value = [] + } + + return { addrResults, addrLoading, searchAddr, selectAddr } +} diff --git a/apps/ops/src/composables/useBestTech.js b/apps/ops/src/composables/useBestTech.js new file mode 100644 index 0000000..49f36a1 --- /dev/null +++ b/apps/ops/src/composables/useBestTech.js @@ -0,0 +1,157 @@ +// ── Find optimal technician for emergency mid-day job insertion ─────────────── +// Scores each tech based on: GPS proximity, current load, skills match, queue fit +// Uses real-time GPS when available, falls back to last job coords or home base +// ───────────────────────────────────────────────────────────────────────────── + +import { MAPBOX_TOKEN } from 'src/config/erpnext' + +// Euclidean distance approximation in km (Montreal latitude) +function distKm (a, b) { + if (!a || !b || (!a[0] && !a[1]) || (!b[0] && !b[1])) return 999 + const dx = (a[0] - b[0]) * 80 // 1° lng ≈ 80 km at 45°N + const dy = (a[1] - b[1]) * 111 // 1° lat ≈ 111 km + return Math.sqrt(dx * dx + dy * dy) +} + +// Get tech's effective current position: GPS > last queued job > home +function techCurrentPos (tech, dateStr) { + if (tech.gpsCoords && tech.gpsOnline) return { coords: tech.gpsCoords, source: 'gps' } + const todayJobs = tech.queue.filter(j => j.scheduledDate === dateStr) + // Find last completed or in-progress job + const done = todayJobs.filter(j => j.status === 'completed' || j.status === 'en-route') + if (done.length) { + const last = done[done.length - 1] + if (last.coords && (last.coords[0] || last.coords[1])) return { coords: last.coords, source: 'lastJob' } + } + // Or next job in queue + if (todayJobs.length) { + const next = todayJobs.find(j => j.coords && (j.coords[0] || j.coords[1])) + if (next) return { coords: next.coords, source: 'nextJob' } + } + return { coords: tech.coords, source: 'home' } +} + +// Compute load: total hours assigned today +function techDayLoad (tech, dateStr) { + return tech.queue + .filter(j => j.scheduledDate === dateStr) + .reduce((sum, j) => sum + (parseFloat(j.duration) || 1), 0) +} + +// Best insertion point in tech's queue (minimizes detour) +function bestInsertionIdx (tech, jobCoords, dateStr) { + const dayJobs = tech.queue.filter(j => j.scheduledDate === dateStr) + if (!dayJobs.length) return 0 + if (!jobCoords || (!jobCoords[0] && !jobCoords[1])) return dayJobs.length + + // Try each insertion point, pick the one with least total detour + let bestIdx = dayJobs.length, bestDetour = Infinity + for (let i = 0; i <= dayJobs.length; i++) { + const prev = i === 0 ? (tech.gpsCoords || tech.coords) : dayJobs[i - 1].coords + const next = i < dayJobs.length ? dayJobs[i].coords : null + const directDist = next ? distKm(prev, next) : 0 + const detour = distKm(prev, jobCoords) + (next ? distKm(jobCoords, next) : 0) - directDist + if (detour < bestDetour) { bestDetour = detour; bestIdx = i } + } + return bestIdx +} + +/** + * Score and rank all technicians for an emergency job. + * + * @param {Object} params + * @param {Array} params.technicians - All available techs + * @param {Array} params.jobCoords - [lng, lat] of the emergency job + * @param {number} params.jobDuration - Hours needed + * @param {Array} params.jobTags - Required skill tags + * @param {string} params.dateStr - YYYY-MM-DD (today) + * @returns {Array} Ranked techs: [{ tech, score, distance, load, insertIdx, reasons }] + */ +export function rankTechs ({ technicians, jobCoords, jobDuration = 1, jobTags = [], dateStr }) { + const hasCoords = jobCoords && (jobCoords[0] || jobCoords[1]) + const candidates = technicians.filter(t => t.status !== 'off' && t.status !== 'unavailable') + + const scored = candidates.map(tech => { + const pos = techCurrentPos(tech, dateStr) + const distance = hasCoords ? distKm(pos.coords, jobCoords) : 999 + const load = techDayLoad(tech, dateStr) + const remainingCap = Math.max(0, 8 - load) + const insertIdx = hasCoords ? bestInsertionIdx(tech, jobCoords, dateStr) : tech.queue.filter(j => j.scheduledDate === dateStr).length + + // ── Scoring (lower = better) ── + let score = 0 + const reasons = [] + + // 1. Proximity (weight: 40%) — distance in km, normalized to ~0-100 + const proxScore = Math.min(distance, 100) + score += proxScore * 4 + if (distance < 5) reasons.push(`📍 ${distance.toFixed(1)} km (très proche)`) + else if (distance < 15) reasons.push(`📍 ${distance.toFixed(1)} km`) + else reasons.push(`📍 ${distance.toFixed(1)} km (loin)`) + + // 2. Load balance (weight: 30%) — prefer techs with capacity + const loadScore = load * 10 // 0-80 range + score += loadScore * 3 + if (remainingCap < jobDuration) { + score += 500 // Heavy penalty: can't fit the job + reasons.push(`⚠ Surchargé (${load.toFixed(1)}h/${8}h)`) + } else if (load < 4) { + reasons.push(`✓ Dispo (${load.toFixed(1)}h/${8}h)`) + } else { + reasons.push(`◐ Chargé (${load.toFixed(1)}h/${8}h)`) + } + + // 3. Skills match (weight: 20%) + if (jobTags.length) { + const techTags = tech.tags || [] + const missing = jobTags.filter(t => !techTags.includes(t)) + score += missing.length * 200 + if (missing.length) reasons.push(`⚠ Manque: ${missing.join(', ')}`) + else reasons.push('✓ Skills OK') + } + + // 4. GPS freshness bonus (weight: 10%) — trust live GPS more + if (pos.source === 'gps') { + score -= 20 // Bonus for having live GPS + reasons.push('🛰 GPS en direct') + } else { + reasons.push(`📌 Position: ${pos.source === 'home' ? 'domicile' : 'estimée'}`) + } + + return { tech, score, distance, load, remainingCap, insertIdx, posSource: pos.source, reasons } + }) + + return scored.sort((a, b) => a.score - b.score) +} + +/** + * Get real driving times from Mapbox for top N candidates (optional refinement). + * Updates the distance/score with actual driving duration. + */ +export async function refineWithDrivingTimes (ranked, jobCoords, topN = 3) { + if (!jobCoords || (!jobCoords[0] && !jobCoords[1])) return ranked + const top = ranked.slice(0, topN) + const rest = ranked.slice(topN) + + const refined = await Promise.all(top.map(async (r) => { + const techPos = r.tech.gpsCoords || r.tech.coords + if (!techPos || (!techPos[0] && !techPos[1])) return r + try { + const url = `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${techPos[0]},${techPos[1]};${jobCoords[0]},${jobCoords[1]}?overview=false&access_token=${MAPBOX_TOKEN}` + const res = await fetch(url) + const data = await res.json() + if (data.routes?.[0]) { + const mins = Math.round(data.routes[0].duration / 60) + const km = (data.routes[0].distance / 1000).toFixed(1) + r.drivingMins = mins + r.drivingKm = km + // Replace proximity score with actual driving time + r.score = mins * 4 + r.load * 30 + (r.reasons.some(r => r.includes('Manque')) ? 200 : 0) + r.reasons[0] = `🚗 ${mins} min (${km} km)` + } + } catch { /* keep euclidean estimate */ } + return r + })) + + return [...refined.sort((a, b) => a.score - b.score), ...rest] +} diff --git a/apps/ops/src/composables/useConversations.js b/apps/ops/src/composables/useConversations.js new file mode 100644 index 0000000..b602406 --- /dev/null +++ b/apps/ops/src/composables/useConversations.js @@ -0,0 +1,204 @@ +import { ref, computed } from 'vue' +import { useAuthStore } from 'src/stores/auth' + +const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' + +function agentHeaders (extra = {}) { + try { + const email = useAuthStore().user + if (email && email !== 'authenticated') return { 'Content-Type': 'application/json', 'X-Authentik-Email': email, ...extra } + } catch {} + return { 'Content-Type': 'application/json', ...extra } +} + +const conversations = ref([]) +const discussions = ref([]) +const activeToken = ref(null) +const activeDiscussion = ref(null) +const activeConv = ref(null) +const showAll = ref(true) +const loading = ref(false) +const panelOpen = ref(false) +const selectedIds = ref(new Set()) +let sseSource = null + +export function useConversations () { + async function fetchList () { + loading.value = true + try { + const url = showAll.value ? `${HUB_URL}/conversations?all=1` : `${HUB_URL}/conversations` + const res = await fetch(url) + if (res.ok) { + const data = await res.json() + conversations.value = data.conversations || [] + discussions.value = data.discussions || [] + } + } catch {} + loading.value = false + } + + function openDiscussion (disc) { + activeDiscussion.value = disc; activeToken.value = null; activeConv.value = null + const active = disc.conversations.find(c => c.status === 'active') + if (active) activeToken.value = active.token + connectDiscussionSSE(disc.conversations.map(c => c.token)) + } + + async function openConversation (token) { + activeToken.value = token + try { const res = await fetch(`${HUB_URL}/conversations/${token}`); if (res.ok) activeConv.value = await res.json() } catch {} + connectSSE(token) + } + + function connectDiscussionSSE (tokens) { + if (sseSource) sseSource.close() + sseSource = new EventSource(`${HUB_URL}/sse?topics=${tokens.map(t => 'conv:' + t).concat(['conversations']).join(',')}`) + sseSource.addEventListener('conv-message', e => { + try { + const data = JSON.parse(e.data) + if (activeDiscussion.value && data.message) { + const msg = { ...data.message, convToken: data.token } + if (!activeDiscussion.value.messages.find(m => m.id === msg.id)) activeDiscussion.value.messages.push(msg) + } + updateListFromMessage(data) + } catch {} + }) + sseSource.addEventListener('conv-closed', handleConvClosed) + sseSource.addEventListener('conv-deleted', () => fetchList()) + } + + function updateListFromMessage (data) { + const idx = conversations.value.findIndex(c => c.token === data.token) + if (idx >= 0) { + conversations.value[idx].lastMessage = data.message + conversations.value[idx].lastActivity = data.message.ts + conversations.value[idx].messageCount++ + } else fetchList() + } + + function handleConvClosed (e) { + try { + const data = JSON.parse(e.data) + const idx = conversations.value.findIndex(c => c.token === data.token) + if (idx >= 0) conversations.value[idx].status = 'closed' + if (activeConv.value && data.token === activeToken.value) activeConv.value.status = 'closed' + } catch {} + } + + function connectSSE (token) { + if (sseSource) sseSource.close() + sseSource = new EventSource(`${HUB_URL}/sse?topics=conv:${token},conversations`) + sseSource.addEventListener('conv-message', e => { + try { + const data = JSON.parse(e.data) + if (activeConv.value && data.token === activeToken.value && data.message) { + if (!activeConv.value.messages.find(m => m.id === data.message.id)) activeConv.value.messages.push(data.message) + } + updateListFromMessage(data) + } catch {} + }) + sseSource.addEventListener('conv-closed', handleConvClosed) + sseSource.addEventListener('conv-deleted', () => fetchList()) + } + + function connectGlobalSSE () { + if (sseSource) sseSource.close() + sseSource = new EventSource(`${HUB_URL}/sse?topics=conversations`) + sseSource.addEventListener('conv-message', e => { try { updateListFromMessage(JSON.parse(e.data)) } catch {} }) + sseSource.addEventListener('conv-deleted', () => fetchList()) + } + + async function sendMessage (token, text) { + const res = await fetch(`${HUB_URL}/conversations/${token}/messages`, { method: 'POST', headers: agentHeaders(), body: JSON.stringify({ text }) }) + if (!res.ok) throw new Error('Failed to send') + return res.json() + } + + async function startConversation ({ phone, customer, customerName, subject }) { + const res = await fetch(`${HUB_URL}/conversations`, { method: 'POST', headers: agentHeaders(), body: JSON.stringify({ phone, customer, customerName, subject }) }) + if (!res.ok) throw new Error('Failed to create conversation') + const data = await res.json(); await fetchList(); return data + } + + async function closeConversation (token) { + await fetch(`${HUB_URL}/conversations/${token}/close`, { method: 'POST', headers: agentHeaders() }) + if (activeConv.value && activeToken.value === token) activeConv.value.status = 'closed' + const idx = conversations.value.findIndex(c => c.token === token) + if (idx >= 0) conversations.value[idx].status = 'closed' + } + + async function deleteDiscussion (disc) { + const res = await fetch(`${HUB_URL}/conversations/discussion`, { + method: 'DELETE', headers: agentHeaders(), + body: JSON.stringify({ phone: disc.phone, date: disc.id.includes(':active') ? 'active' : disc.date }), + }) + if (!res.ok) throw new Error('Failed to delete') + clearActiveIfMatch(disc); await fetchList() + } + + async function bulkDelete (discs) { + const res = await fetch(`${HUB_URL}/conversations/bulk`, { + method: 'DELETE', headers: agentHeaders(), + body: JSON.stringify({ discussions: discs.map(d => ({ tokens: d.conversations.map(c => c.token) })) }), + }) + if (!res.ok) throw new Error('Failed to bulk delete') + const data = await res.json() + for (const d of discs) clearActiveIfMatch(d) + selectedIds.value.clear(); await fetchList(); return data + } + + async function archiveDiscussion (disc) { + const res = await fetch(`${HUB_URL}/conversations/archive`, { + method: 'POST', headers: agentHeaders(), + body: JSON.stringify({ tokens: disc.conversations.map(c => c.token) }), + }) + if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || 'Archive failed') } + const data = await res.json(); clearActiveIfMatch(disc); await fetchList(); return data + } + + async function bulkArchive (discs) { + const results = [] + for (const d of discs) { + const res = await fetch(`${HUB_URL}/conversations/archive`, { + method: 'POST', headers: agentHeaders(), + body: JSON.stringify({ tokens: d.conversations.map(c => c.token) }), + }) + if (res.ok) results.push(await res.json()) + } + selectedIds.value.clear(); await fetchList(); return results + } + + function clearActiveIfMatch (disc) { + if (activeDiscussion.value?.id === disc.id) { activeDiscussion.value = null; activeToken.value = null; activeConv.value = null } + } + + function toggleSelect (discId) { + const s = new Set(selectedIds.value) + s.has(discId) ? s.delete(discId) : s.add(discId) + selectedIds.value = s + } + + function selectAll () { selectedIds.value = new Set(discussions.value.map(d => d.id)) } + function clearSelection () { selectedIds.value = new Set() } + + const selectedDiscussions = computed(() => discussions.value.filter(d => selectedIds.value.has(d.id))) + + function disconnectSSE () { if (sseSource) { sseSource.close(); sseSource = null } } + + function goBack () { + activeToken.value = null; activeConv.value = null; activeDiscussion.value = null + if (sseSource) sseSource.close() + connectGlobalSSE() + } + + const activeCount = computed(() => discussions.value.filter(d => d.status === 'active').length) + + return { + conversations, discussions, activeToken, activeDiscussion, activeConv, + loading, panelOpen, showAll, selectedIds, selectedDiscussions, + fetchList, openDiscussion, openConversation, sendMessage, startConversation, + closeConversation, deleteDiscussion, bulkDelete, archiveDiscussion, bulkArchive, + toggleSelect, selectAll, clearSelection, + goBack, disconnectSSE, connectGlobalSSE, activeCount, HUB_URL, + } +} diff --git a/apps/ops/src/composables/useDeviceStatus.js b/apps/ops/src/composables/useDeviceStatus.js index b69514a..53a3d41 100644 --- a/apps/ops/src/composables/useDeviceStatus.js +++ b/apps/ops/src/composables/useDeviceStatus.js @@ -8,9 +8,17 @@ const HUB_URL = (window.location.hostname === 'localhost') ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' -// Cache: serial → { data, ts } -const cache = new Map() +// Singleton state — survives component remounts so cached data shows instantly +const cache = new Map() // serial → { data, ts } +const hostsCache = new Map() // serial → { data, ts } +const oltCache = new Map() // serial → { data, ts } +const deviceMap = ref(new Map()) // serial → { ...summarizedDevice } +const oltMap = ref(new Map()) // serial → { status, rxPowerOlt, ... } +const loading = ref(false) +const error = ref(null) const CACHE_TTL = 60_000 // 1 min +const HOSTS_TTL = 120_000 // 2 min +const OLT_TTL = 60_000 // 1 min /** * Fetch live device info from GenieACS for a list of equipment objects. @@ -18,45 +26,54 @@ const CACHE_TTL = 60_000 // 1 min * Returns a reactive Map. */ export function useDeviceStatus () { - const deviceMap = ref(new Map()) // serial → { ...summarizedDevice } - const loading = ref(false) - const error = ref(null) async function fetchStatus (equipmentList) { if (!equipmentList || !equipmentList.length) return - loading.value = true error.value = null const now = Date.now() const toFetch = [] const map = new Map(deviceMap.value) + // Immediately populate from cache so UI shows buffered data for (const eq of equipmentList) { const serial = eq.serial_number if (!serial) continue const cached = cache.get(serial) - if (cached && (now - cached.ts) < CACHE_TTL) { + if (cached) { + // Always show cached data immediately (even if stale) map.set(serial, cached.data) - } else { - toFetch.push(serial) + // Only skip re-fetch if cache is fresh + if ((now - cached.ts) < CACHE_TTL) continue } + toFetch.push(serial) } + // Apply cached data right away before network fetch + if (map.size > deviceMap.value.size) { + deviceMap.value = new Map(map) + } + + if (!toFetch.length) return + // Batch lookups (GenieACS NBI doesn't support batch, so parallel individual) - if (toFetch.length) { - const results = await Promise.allSettled( - toFetch.map(serial => - fetch(`${HUB_URL}/devices/lookup?serial=${encodeURIComponent(serial)}`) - .then(r => r.ok ? r.json() : null) - .then(data => ({ serial, data: Array.isArray(data) && data.length ? data[0] : null })) - .catch(() => ({ serial, data: null })) - ) + loading.value = true + const results = await Promise.allSettled( + toFetch.map(serial => + fetch(`${HUB_URL}/devices/lookup?serial=${encodeURIComponent(serial)}`) + .then(r => r.ok ? r.json() : null) + .then(data => ({ serial, data: Array.isArray(data) && data.length ? data[0] : null })) + .catch(() => ({ serial, data: null })) ) - for (const r of results) { - if (r.status === 'fulfilled' && r.value.data) { - map.set(r.value.serial, r.value.data) - cache.set(r.value.serial, { data: r.value.data, ts: now }) - } + ) + for (const r of results) { + if (r.status === 'fulfilled' && r.value.data) { + // Merge fresh data on top of existing cached data (don't clear fields) + const existing = map.get(r.value.serial) + const fresh = r.value.data + const merged = existing ? { ...existing, ...fresh } : fresh + map.set(r.value.serial, merged) + cache.set(r.value.serial, { data: merged, ts: now }) } } @@ -68,11 +85,92 @@ export function useDeviceStatus () { return deviceMap.value.get(serial) || null } + /** + * Fetch OLT SNMP status for a serial number. + * Returns { status: 'online'|'offline', rxPowerOlt, distance, ... } or null. + */ + async function fetchOltStatus (serial) { + if (!serial) return null + const now = Date.now() + const cached = oltCache.get(serial) + if (cached && (now - cached.ts) < OLT_TTL) return cached.data + + try { + const res = await fetch(`${HUB_URL}/olt/onus?serial=${encodeURIComponent(serial)}`) + if (!res.ok) return cached?.data || null + const data = await res.json() + if (data && !data.error) { + oltCache.set(serial, { data, ts: now }) + const map = new Map(oltMap.value) + map.set(serial, data) + oltMap.value = map + return data + } + } catch {} + return cached?.data || null + } + + function getOltData (serial) { + return oltMap.value.get(serial) || null + } + function isOnline (serial) { const d = getDevice(serial) - if (!d || !d.lastInform) return null // unknown - const age = Date.now() - new Date(d.lastInform).getTime() - return age < 5 * 60 * 1000 // online if last inform < 5 min ago + const tr069Online = d && d.lastInform + ? (Date.now() - new Date(d.lastInform).getTime()) < 15 * 60 * 1000 + : null + + // Cross-reference OLT SNMP — ground truth for fiber connectivity + const olt = getOltData(serial) + const oltOnline = olt ? olt.status === 'online' : null + + // OLT is authoritative: if OLT says online, device has internet regardless of TR-069 + if (oltOnline === true) return true + if (oltOnline === false) return false + // No OLT data available — fall back to TR-069 only + return tr069Online + } + + /** + * Combined status with source info for UI display. + * Returns { online: bool|null, source: 'both'|'olt'|'tr069'|'unknown', label: string, detail: string } + */ + function combinedStatus (serial) { + const d = getDevice(serial) + const tr069Online = d && d.lastInform + ? (Date.now() - new Date(d.lastInform).getTime()) < 15 * 60 * 1000 + : null + const olt = getOltData(serial) + const oltOnline = olt ? olt.status === 'online' : null + + // Both agree online + if (tr069Online === true && oltOnline === true) { + return { online: true, source: 'both', label: 'En ligne', detail: 'TR-069 + Fibre OK' } + } + // OLT says online, TR-069 stale — device is online, management channel stale + if (oltOnline === true && tr069Online !== true) { + return { online: true, source: 'olt', label: 'En ligne', detail: 'Fibre OK · TR-069 inactif' } + } + // Both agree offline + if (tr069Online === false && oltOnline === false) { + return { online: false, source: 'both', label: 'Hors ligne', detail: 'TR-069 + Fibre hors ligne' } + } + // OLT says offline, TR-069 unknown + if (oltOnline === false && tr069Online === null) { + return { online: false, source: 'olt', label: 'Hors ligne', detail: 'Fibre hors ligne' } + } + // OLT says offline, TR-069 says online (unlikely but possible during transition) + if (oltOnline === false && tr069Online === true) { + return { online: false, source: 'olt', label: 'Hors ligne', detail: 'Fibre coupée · TR-069 résiduel' } + } + // No OLT data, TR-069 only + if (tr069Online === true) { + return { online: true, source: 'tr069', label: 'En ligne', detail: 'TR-069 actif' } + } + if (tr069Online === false) { + return { online: false, source: 'tr069', label: 'Hors ligne', detail: 'TR-069 inactif · OLT non vérifié' } + } + return { online: null, source: 'unknown', label: 'Inconnu', detail: 'Aucune donnée' } } function signalQuality (serial) { @@ -128,27 +226,41 @@ export function useDeviceStatus () { const err = await res.json().catch(() => ({})) throw new Error(err.error || 'Refresh failed') } - // Invalidate cache so next fetch gets fresh data - cache.delete(serial) + // Mark cache stale so next fetch refreshes, but keep data for instant display + const cached = cache.get(serial) + if (cached) cached.ts = 0 return res.json() } async function fetchHosts (serial, refresh = false) { + // Return cached hosts instantly if available and not forcing refresh + const cached = hostsCache.get(serial) + if (cached && !refresh && (Date.now() - cached.ts) < HOSTS_TTL) { + return cached.data + } + const d = getDevice(serial) - if (!d) return null + if (!d) return cached?.data || null // return stale cache if device not resolved yet + const url = `${HUB_URL}/devices/${encodeURIComponent(d._id)}/hosts${refresh ? '?refresh' : ''}` const res = await fetch(url) - if (!res.ok) return null - return res.json() + if (!res.ok) return cached?.data || null + const data = await res.json() + hostsCache.set(serial, { data, ts: Date.now() }) + return data } return { deviceMap: readonly(deviceMap), + oltMap: readonly(oltMap), loading: readonly(loading), error: readonly(error), fetchStatus, + fetchOltStatus, getDevice, + getOltData, isOnline, + combinedStatus, signalQuality, rebootDevice, refreshDeviceParams, diff --git a/apps/ops/src/composables/useGpsTracking.js b/apps/ops/src/composables/useGpsTracking.js index 51e398a..499e7dd 100644 --- a/apps/ops/src/composables/useGpsTracking.js +++ b/apps/ops/src/composables/useGpsTracking.js @@ -1,5 +1,5 @@ import { ref } from 'vue' -import { fetchDevices, fetchPositions, createTraccarSession } from 'src/api/traccar' +import { fetchDevices, fetchPositions } from 'src/api/traccar' let __gpsStarted = false let __gpsInterval = null @@ -42,6 +42,7 @@ export function useGpsTracking (technicians) { if (!deviceIds.length) return const positions = await fetchPositions(deviceIds) _applyPositions(positions) + // Mark techs with no position as offline Object.values(_techsByDevice).forEach(t => { if (!positions.find(p => _techsByDevice[p.deviceId] === t)) t.gpsOnline = false }) @@ -49,54 +50,16 @@ export function useGpsTracking (technicians) { finally { __gpsPolling = false } } - let __ws = null - let __wsBackoff = 1000 - - function _connectWs () { - if (__ws) return - const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:' - const url = proto + '//' + window.location.host + '/traccar/api/socket' - try { __ws = new WebSocket(url) } catch { return } - __ws.onopen = () => { - __wsBackoff = 1000 - if (__gpsInterval) { clearInterval(__gpsInterval); __gpsInterval = null } - } - __ws.onmessage = (e) => { - try { - const data = JSON.parse(e.data) - if (data.positions?.length) { - _buildTechDeviceMap() - _applyPositions(data.positions) - } - } catch { /* parse error */ } - } - __ws.onerror = () => {} - __ws.onclose = () => { - __ws = null - if (!__gpsStarted) return - if (!__gpsInterval) { - __gpsInterval = setInterval(pollGps, 30000) - } - setTimeout(_connectWs, __wsBackoff) - __wsBackoff = Math.min(__wsBackoff * 2, 60000) - } - } - async function startGpsTracking () { if (__gpsStarted) return __gpsStarted = true await pollGps() - const sessionOk = await createTraccarSession() - if (sessionOk) { - _connectWs() - } else { - __gpsInterval = setInterval(pollGps, 30000) - } + __gpsInterval = setInterval(pollGps, 30000) } function stopGpsTracking () { + __gpsStarted = false if (__gpsInterval) { clearInterval(__gpsInterval); __gpsInterval = null } - if (__ws) { const ws = __ws; __ws = null; ws.onclose = null; ws.close() } } return { traccarDevices, pollGps, startGpsTracking, stopGpsTracking } diff --git a/apps/ops/src/composables/useHelpers.js b/apps/ops/src/composables/useHelpers.js index b916161..f00045b 100644 --- a/apps/ops/src/composables/useHelpers.js +++ b/apps/ops/src/composables/useHelpers.js @@ -98,13 +98,78 @@ export function sortJobsByTime (jobs) { }) } +// ── ERPNext ↔ internal status mapping ── +// ERPNext stores French labels; frontend uses internal codes +export const ERP_STATUS_TO_INTERNAL = { + 'Disponible': 'available', + 'En route': 'en-route', + 'En pause': 'off', + 'Hors ligne': 'offline', + 'Inactif': 'inactive', +} +export const INTERNAL_STATUS_TO_ERP = Object.fromEntries( + Object.entries(ERP_STATUS_TO_INTERNAL).map(([k, v]) => [v, k]) +) +export function normalizeStatus (erpStatus) { + return ERP_STATUS_TO_INTERNAL[erpStatus] || erpStatus || 'available' +} +export function toErpStatus (internalStatus) { + return INTERNAL_STATUS_TO_ERP[internalStatus] || internalStatus || 'Disponible' +} + +// ── Weekly schedule helpers ── +const DAY_KEYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'] +export const DAY_LABELS = { mon: 'Lun', tue: 'Mar', wed: 'Mer', thu: 'Jeu', fri: 'Ven', sat: 'Sam', sun: 'Dim' } +export const WEEK_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] +export const DEFAULT_WEEKLY_SCHEDULE = { + mon: { start: '08:00', end: '16:00' }, tue: { start: '08:00', end: '16:00' }, + wed: { start: '08:00', end: '16:00' }, thu: { start: '08:00', end: '16:00' }, + fri: { start: '08:00', end: '16:00' }, sat: null, sun: null, +} +export const SCHEDULE_PRESETS = [ + { key: 'standard', label: '5×8h (lun-ven)', schedule: { ...DEFAULT_WEEKLY_SCHEDULE } }, + { key: '4x10', label: '4×10h (lun-jeu)', schedule: { + mon: { start: '07:00', end: '17:00' }, tue: { start: '07:00', end: '17:00' }, + wed: { start: '07:00', end: '17:00' }, thu: { start: '07:00', end: '17:00' }, + fri: null, sat: null, sun: null, + }}, + { key: '3x12', label: '3×12h (lun-mer)', schedule: { + mon: { start: '06:00', end: '18:00' }, tue: { start: '06:00', end: '18:00' }, + wed: { start: '06:00', end: '18:00' }, thu: null, fri: null, sat: null, sun: null, + }}, +] +export function parseWeeklySchedule (jsonStr) { + if (!jsonStr) return { ...DEFAULT_WEEKLY_SCHEDULE } + try { + const parsed = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr + if (typeof parsed !== 'object' || parsed === null) return { ...DEFAULT_WEEKLY_SCHEDULE } + return parsed + } catch { return { ...DEFAULT_WEEKLY_SCHEDULE } } +} +export function techDaySchedule (tech, dateStr) { + const d = new Date(dateStr + 'T12:00:00') + const key = DAY_KEYS[d.getDay()] + const sched = tech.weeklySchedule || DEFAULT_WEEKLY_SCHEDULE + const day = sched[key] + if (!day) return null + const startH = timeToH(day.start || '08:00') + const endH = timeToH(day.end || '16:00') + return { start: day.start, end: day.end, startH, endH, hours: endH - startH } +} +export function techDayCapacityH (tech, dateStr) { + const s = techDaySchedule(tech, dateStr) + return s ? s.hours : 0 +} + // Status helpers export const STATUS_MAP = { 'available': { cls:'st-available', label:'Disponible' }, 'en-route': { cls:'st-enroute', label:'En route' }, 'busy': { cls:'st-busy', label:'En cours' }, 'in progress': { cls:'st-busy', label:'En cours' }, - 'off': { cls:'st-off', label:'Hors shift' }, + 'off': { cls:'st-off', label:'En pause' }, + 'offline': { cls:'st-off', label:'Hors ligne' }, + 'inactive': { cls:'st-inactive', label:'Inactif' }, } export function stOf (t) { return STATUS_MAP[(t.status||'').toLowerCase()] || STATUS_MAP['available'] } diff --git a/apps/ops/src/composables/usePermissions.js b/apps/ops/src/composables/usePermissions.js new file mode 100644 index 0000000..9ab2aa1 --- /dev/null +++ b/apps/ops/src/composables/usePermissions.js @@ -0,0 +1,98 @@ +import { ref, computed } from 'vue' + +const HUB_URL = (window.location.hostname === 'localhost') + ? 'http://localhost:3300' + : 'https://msg.gigafibre.ca' + +// Singleton state — shared across all components +const permissions = ref(null) // { email, username, name, groups, is_superuser, capabilities, overrides } +const loading = ref(false) +const error = ref(null) + +let fetchPromise = null // Prevent duplicate fetches + +/** + * Fetch effective permissions for the current user from targo-hub. + * Reads X-Authentik-Email header (injected by Traefik). + * @param {string} email — user email (from Authentik header or session) + */ +async function loadPermissions (email) { + if (!email) return + if (fetchPromise) return fetchPromise + + loading.value = true + error.value = null + + fetchPromise = fetch(`${HUB_URL}/auth/permissions?email=${encodeURIComponent(email)}`) + .then(r => { + if (!r.ok) throw new Error('Permission fetch failed: ' + r.status) + return r.json() + }) + .then(data => { + permissions.value = data + }) + .catch(e => { + console.error('[usePermissions]', e.message) + error.value = e.message + // Fallback: allow everything for superusers if fetch fails + permissions.value = null + }) + .finally(() => { + loading.value = false + fetchPromise = null + }) + + return fetchPromise +} + +/** + * Check if the current user has a specific capability. + * Superusers always return true. + * Returns false if permissions haven't loaded yet (safe default). + */ +function can (capability) { + if (!permissions.value) return false + if (permissions.value.is_superuser) return true + return permissions.value.capabilities?.[capability] === true +} + +/** + * Check if the user has ANY of the listed capabilities. + */ +function canAny (...capabilities) { + return capabilities.some(c => can(c)) +} + +/** + * Check if the user belongs to a specific group. + */ +function hasGroup (groupName) { + return permissions.value?.groups?.includes(groupName) || false +} + +// Computed helpers +const userGroups = computed(() => permissions.value?.groups || []) +const userName = computed(() => permissions.value?.name || '') +const userEmail = computed(() => permissions.value?.email || '') +const isSuperuser = computed(() => permissions.value?.is_superuser || false) +const isAdmin = computed(() => hasGroup('admin') || isSuperuser.value) +const isLoaded = computed(() => permissions.value !== null) + +export function usePermissions () { + return { + permissions, + loading, + error, + loadPermissions, + can, + canAny, + hasGroup, + userGroups, + userName, + userEmail, + isSuperuser, + isAdmin, + isLoaded, + HUB_URL, + } +} diff --git a/apps/ops/src/composables/useResourceFilter.js b/apps/ops/src/composables/useResourceFilter.js index b726f37..d2849bc 100644 --- a/apps/ops/src/composables/useResourceFilter.js +++ b/apps/ops/src/composables/useResourceFilter.js @@ -3,7 +3,9 @@ import { ref, computed, watch } from 'vue' export function useResourceFilter (store) { const selectedResIds = ref(JSON.parse(localStorage.getItem('sbv2-resIds') || '[]')) const filterStatus = ref(localStorage.getItem('sbv2-filterStatus') || '') + const filterGroup = ref(localStorage.getItem('sbv2-filterGroup') || '') const filterTags = ref(JSON.parse(localStorage.getItem('sbv2-filterTags') || '[]')) + const filterResourceType = ref(localStorage.getItem('sbv2-filterResType') || '') // '' | 'human' | 'material' const searchQuery = ref('') const techSort = ref(localStorage.getItem('sbv2-techSort') || 'default') const manualOrder = ref(JSON.parse(localStorage.getItem('sbv2-techOrder') || '[]')) @@ -13,23 +15,77 @@ export function useResourceFilter (store) { watch(selectedResIds, v => localStorage.setItem('sbv2-resIds', JSON.stringify(v)), { deep: true }) watch(filterStatus, v => localStorage.setItem('sbv2-filterStatus', v)) + watch(filterGroup, v => localStorage.setItem('sbv2-filterGroup', v)) + watch(filterResourceType, v => localStorage.setItem('sbv2-filterResType', v)) watch(techSort, v => localStorage.setItem('sbv2-techSort', v)) + // Unique groups extracted from technicians + const availableGroups = computed(() => { + const groups = new Set() + for (const t of store.technicians) { + if (t.group) groups.add(t.group) + } + return [...groups].sort() + }) + + // Resource categories for material resources + const availableCategories = computed(() => { + const cats = new Set() + for (const t of store.technicians) { + if (t.resourceCategory) cats.add(t.resourceCategory) + } + return [...cats].sort() + }) + + // Count by type + const humanCount = computed(() => store.technicians.filter(t => t.resourceType !== 'material').length) + const materialCount = computed(() => store.technicians.filter(t => t.resourceType === 'material').length) + + const showInactive = ref(false) + const filteredResources = computed(() => { let list = store.technicians + // Hide permanently inactive techs unless explicitly showing them or filtering for them + // Temporarily absent (off) techs stay visible so absence blocks render on the timeline + if (!showInactive.value && filterStatus.value !== 'inactive') list = list.filter(t => t.status !== 'inactive') + if (filterResourceType.value) list = list.filter(t => (t.resourceType || 'human') === filterResourceType.value) if (searchQuery.value) { const q = searchQuery.value.toLowerCase(); list = list.filter(t => t.fullName.toLowerCase().includes(q)) } if (filterStatus.value) list = list.filter(t => (t.status||'available') === filterStatus.value) + if (filterGroup.value) list = list.filter(t => t.group === filterGroup.value) if (selectedResIds.value.length) list = list.filter(t => selectedResIds.value.includes(t.id)) if (filterTags.value.length) list = list.filter(t => filterTags.value.every(ft => (t.tags||[]).includes(ft))) - if (techSort.value === 'alpha') { - list = [...list].sort((a, b) => a.fullName.split(' ').pop().toLowerCase().localeCompare(b.fullName.split(' ').pop().toLowerCase())) - } else if (techSort.value === 'manual' && manualOrder.value.length) { - const order = manualOrder.value - list = [...list].sort((a, b) => (order.indexOf(a.id) === -1 ? 999 : order.indexOf(a.id)) - (order.indexOf(b.id) === -1 ? 999 : order.indexOf(b.id))) - } + // Sort: humans first, then material; within each, apply chosen sort + list = [...list].sort((a, b) => { + const aType = a.resourceType === 'material' ? 1 : 0 + const bType = b.resourceType === 'material' ? 1 : 0 + if (aType !== bType) return aType - bType + if (techSort.value === 'alpha') return a.fullName.split(' ').pop().toLowerCase().localeCompare(b.fullName.split(' ').pop().toLowerCase()) + if (techSort.value === 'manual' && manualOrder.value.length) { + const order = manualOrder.value + return (order.indexOf(a.id) === -1 ? 999 : order.indexOf(a.id)) - (order.indexOf(b.id) === -1 ? 999 : order.indexOf(b.id)) + } + return 0 + }) return list }) + // Resources grouped by tech_group for visual grouping + const groupedResources = computed(() => { + const groups = new Map() + for (const t of filteredResources.value) { + const g = t.group || '' + if (!groups.has(g)) groups.set(g, []) + groups.get(g).push(t) + } + // Sort: named groups first (alphabetically), then ungrouped last + const sorted = [...groups.entries()].sort((a, b) => { + if (!a[0] && b[0]) return 1 + if (a[0] && !b[0]) return -1 + return a[0].localeCompare(b[0]) + }) + return sorted.map(([name, techs]) => ({ name, label: name || 'Non assigné', techs })) + }) + function onTechReorderStart (e, tech) { dragReorderTech.value = tech e.dataTransfer.effectAllowed = 'move' @@ -52,11 +108,15 @@ export function useResourceFilter (store) { const idx = tempSelectedIds.value.indexOf(id) if (idx >= 0) tempSelectedIds.value.splice(idx, 1); else tempSelectedIds.value.push(id) } - function clearFilters () { selectedResIds.value = []; filterStatus.value = ''; searchQuery.value = ''; filterTags.value = []; localStorage.removeItem('sbv2-filterTags') } + function clearFilters () { selectedResIds.value = []; filterStatus.value = ''; filterGroup.value = ''; filterResourceType.value = ''; searchQuery.value = ''; filterTags.value = []; showInactive.value = false; localStorage.removeItem('sbv2-filterTags'); localStorage.removeItem('sbv2-filterGroup'); localStorage.removeItem('sbv2-filterResType') } + + // Count of inactive techs (for UI indicator) + const inactiveCount = computed(() => store.technicians.filter(t => !t.active).length) return { - selectedResIds, filterStatus, filterTags, searchQuery, techSort, manualOrder, - filteredResources, resSelectorOpen, tempSelectedIds, dragReorderTech, + selectedResIds, filterStatus, filterGroup, filterResourceType, filterTags, searchQuery, techSort, manualOrder, + showInactive, inactiveCount, humanCount, materialCount, availableCategories, + filteredResources, groupedResources, availableGroups, resSelectorOpen, tempSelectedIds, dragReorderTech, openResSelector, applyResSelector, toggleTempRes, clearFilters, onTechReorderStart, onTechReorderDrop, } diff --git a/apps/ops/src/composables/useScanner.js b/apps/ops/src/composables/useScanner.js new file mode 100644 index 0000000..00976c2 --- /dev/null +++ b/apps/ops/src/composables/useScanner.js @@ -0,0 +1,123 @@ +import { ref } from 'vue' + +const HUB_BASE = window.location.hostname === 'localhost' + ? 'http://localhost:3300' + : 'https://msg.gigafibre.ca' + +export function useScanner () { + const barcodes = ref([]) + const scanning = ref(false) + const error = ref(null) + const lastPhoto = ref(null) + + async function processPhoto (file) { + if (!file) return [] + error.value = null + scanning.value = true + const found = [] + try { + const thumbUrl = await resizeImage(file, 400) + lastPhoto.value = thumbUrl + const aiImage = await resizeImage(file, 1600, 0.92) + const res = await fetch(`${HUB_BASE}/vision/barcodes`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ image: aiImage }), + }) + if (!res.ok) throw new Error('Vision scan failed: ' + res.status) + const data = await res.json() + const existing = new Set(barcodes.value.map(b => b.value)) + for (const code of (data.barcodes || [])) { + if (barcodes.value.length >= 5) break + if (!existing.has(code)) { + existing.add(code) + barcodes.value.push({ value: code }) + found.push(code) + } + } + if (!found.length) error.value = 'Aucun code detecte — rapprochez-vous ou ameliorez la mise au point' + } catch (e) { + error.value = e.message || 'Erreur' + } finally { + scanning.value = false + } + return found + } + + /** + * Smart equipment label scan — returns structured fields + * { brand, model, serial_number, mac_address, gpon_sn, hw_version, equipment_type, barcodes } + */ + async function scanEquipmentLabel (file) { + if (!file) return null + error.value = null + scanning.value = true + try { + const thumbUrl = await resizeImage(file, 400) + lastPhoto.value = thumbUrl + const aiImage = await resizeImage(file, 1600, 0.92) + const res = await fetch(`${HUB_BASE}/vision/equipment`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ image: aiImage }), + }) + if (!res.ok) throw new Error('Vision scan failed: ' + res.status) + const data = await res.json() + // Also populate barcodes list for display + if (data.barcodes?.length) { + const existing = new Set(barcodes.value.map(b => b.value)) + for (const code of data.barcodes) { + if (barcodes.value.length >= 5) break + if (!existing.has(code)) { + existing.add(code) + barcodes.value.push({ value: code }) + } + } + } + if (data.serial_number) { + const existing = new Set(barcodes.value.map(b => b.value)) + if (!existing.has(data.serial_number)) { + barcodes.value.push({ value: data.serial_number }) + } + } + if (!data.serial_number && !data.barcodes?.length) { + error.value = 'Aucun identifiant detecte — rapprochez-vous ou ameliorez la mise au point' + } + return data + } catch (e) { + error.value = e.message || 'Erreur' + return null + } finally { + scanning.value = false + } + } + + function resizeImage (file, maxDim, quality = 0.85) { + return new Promise((resolve, reject) => { + const img = new Image() + img.onload = () => { + let { width, height } = img + if (width > maxDim || height > maxDim) { + const ratio = Math.min(maxDim / width, maxDim / height) + width = Math.round(width * ratio) + height = Math.round(height * ratio) + } + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + canvas.getContext('2d').drawImage(img, 0, 0, width, height) + resolve(canvas.toDataURL('image/jpeg', quality)) + } + img.onerror = reject + img.src = URL.createObjectURL(file) + }) + } + + function clearBarcodes () { + barcodes.value = [] + error.value = null + lastPhoto.value = null + } + + return { barcodes, scanning, error, lastPhoto, processPhoto, scanEquipmentLabel, clearBarcodes } +} diff --git a/apps/ops/src/composables/useScheduler.js b/apps/ops/src/composables/useScheduler.js index d872f59..ede924c 100644 --- a/apps/ops/src/composables/useScheduler.js +++ b/apps/ops/src/composables/useScheduler.js @@ -1,6 +1,7 @@ // ── Scheduling logic: timeline computation, route cache, job placement ─────── import { ref, computed } from 'vue' -import { localDateStr, timeToH, hToTime, sortJobsByTime, jobSpansDate } from './useHelpers' +import { localDateStr, timeToH, hToTime, sortJobsByTime, jobSpansDate, techDaySchedule, techDayCapacityH } from './useHelpers' +import { ABSENCE_REASONS } from './useTechManagement' export function useScheduler (store, currentView, periodStart, periodDays, dayColumns, pxPerHr, getJobDate, getJobTime, jobColorFn) { const H_START = 7 @@ -24,7 +25,8 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo const cacheKey = `${leadTech.id}||${dayStr}` const legMins = routeLegs.value[cacheKey] const hasHome = !!(leadTech.coords?.[0] && leadTech.coords?.[1]) - let cursor = 8, result = job.startHour ?? 8 + const _parentSched = techDaySchedule(leadTech, dayStr) + let cursor = _parentSched ? _parentSched.startH : 8, result = job.startHour ?? cursor leadJobs.forEach((j, idx) => { const showTravel = idx > 0 || (idx === 0 && hasHome) if (showTravel) { @@ -64,9 +66,54 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo return sortJobsByTime([...primary, ...assists]) } + // ── Absence / schedule-off segments for a tech on a given date ─────────────── + function absenceSegmentsForDate (tech, dateStr) { + const segs = [] + + // 1. Explicit absence (vacation, sick, etc.) + if (tech.status === 'off' && tech.absenceFrom) { + const from = tech.absenceFrom + const until = tech.absenceUntil || from + if (dateStr >= from && dateStr <= until) { + const startH = tech.absenceStartTime ? timeToH(tech.absenceStartTime) : H_START + const endH = tech.absenceEndTime ? timeToH(tech.absenceEndTime) : H_END + const reasonObj = ABSENCE_REASONS.find(r => r.value === tech.absenceReason) || { label: 'Absent', icon: '⏸' } + const left = (startH - H_START) * pxPerHr.value + const width = (endH - startH) * pxPerHr.value + segs.push({ + type: 'absence', startH, endH, + reason: tech.absenceReason, reasonLabel: reasonObj.label, reasonIcon: reasonObj.icon, + from: tech.absenceFrom, until: tech.absenceUntil, techId: tech.id, + style: { left: left + 'px', width: Math.max(18, width) + 'px', top: '4px', bottom: '4px', position: 'absolute' }, + job: { id: `absence-${tech.id}-${dateStr}` }, + }) + return segs // absence covers the day — skip schedule check + } + } + + // 2. Weekly schedule off-day (regular day off like Fridays for 4×10 schedule) + const daySched = techDaySchedule(tech, dateStr) + if (!daySched) { + const left = 0 + const width = (H_END - H_START) * pxPerHr.value + segs.push({ + type: 'absence', startH: H_START, endH: H_END, + reason: 'day_off', reasonLabel: 'Jour de repos', reasonIcon: '📅', + from: null, until: null, techId: tech.id, + style: { left: left + 'px', width: Math.max(18, width) + 'px', top: '4px', bottom: '4px', position: 'absolute' }, + job: { id: `schedoff-${tech.id}-${dateStr}` }, + }) + } + + return segs + } + // ── Day view: schedule blocks with pinned anchors + auto-flow ────────────── function techDayJobsWithTravel (tech) { + _checkCacheInvalidation() const dayStr = localDateStr(periodStart.value) + const segKey = `${tech.id}||${dayStr}||${tech.queue.length}||${(tech.assistJobs||[]).length}` + if (_daySegmentsCache.has(segKey)) return _daySegmentsCache.get(segKey) const cacheKey = `${tech.id}||${dayStr}` const legMins = routeLegs.value[cacheKey] const hasHome = !!(tech.coords?.[0] && tech.coords?.[1]) @@ -84,9 +131,13 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo else flowEntries.push(entry) }) + // Absence blocks act as occupied time (prevent auto-placing jobs there) + const absSegs = absenceSegmentsForDate(tech, dayStr) + const absOccupied = absSegs.map(s => ({ start: s.startH, end: s.endH })) + const pinnedAnchors = flowEntries.filter(e => e.isPinned).map(e => ({ start: e.pinH, end: e.pinH + e.dur })) const placed = [] - const occupied = pinnedAnchors.map(a => ({ ...a })) + const occupied = [...pinnedAnchors.map(a => ({ ...a })), ...absOccupied] const sortedFlow = [...flowEntries].sort((a, b) => { if (a.isPinned && b.isPinned) return a.pinH - b.pinH if (a.isPinned) return -1 @@ -96,7 +147,8 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo sortedFlow.filter(e => e.isPinned).forEach(e => placed.push({ entry: e, startH: e.pinH })) - let cursor = 8, flowIdx = 0 + const daySched = techDaySchedule(tech, dayStr) + let cursor = daySched ? daySched.startH : 8, flowIdx = 0 sortedFlow.filter(e => !e.isPinned).forEach(e => { const legIdx = hasHome ? flowIdx : flowIdx - 1 const routeMin = legMins?.[legIdx >= 0 ? legIdx : 0] @@ -115,18 +167,26 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo flowIdx++ }) + // Inject absence blocks as pseudo-placed entries so travel chains around them + absSegs.forEach(s => { + placed.push({ entry: { job: s.job, dur: s.endH - s.startH, isAssist: false, isPinned: true, pinH: s.startH, _isAbsence: true, _absSeg: s }, startH: s.startH }) + }) + placed.sort((a, b) => a.startH - b.startH) const result = [] let prevEndH = null - placed.forEach((p, pIdx) => { + let legCounter = 0 + placed.forEach((p) => { const { entry, startH } = p - const { job, dur, isAssist, isPinned } = entry - const realJob = isAssist ? job._parentJob : job + const { job, dur, isAssist, isPinned, _isAbsence, _absSeg } = entry + + // Travel gap between previous block end and this block start const travelStart = prevEndH ?? (hasHome ? 8 : null) - if (travelStart != null && startH > travelStart + 0.01) { + if (travelStart != null && startH > travelStart + 0.01 && !_isAbsence) { const gapH = startH - travelStart - const legIdx = hasHome ? pIdx : pIdx - 1 + const realJob = isAssist ? job._parentJob : job + const legIdx = hasHome ? legCounter : legCounter - 1 const routeMin = legMins?.[legIdx >= 0 ? legIdx : 0] const fromRoute = routeMin != null result.push({ @@ -134,16 +194,24 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo style: { left: (travelStart - H_START) * pxPerHr.value + 'px', width: Math.max(8, gapH * pxPerHr.value) + 'px', top: '10px', bottom: '10px', position: 'absolute' }, color: jobColorFn(realJob), }) + legCounter++ + } + + if (_isAbsence) { + // Render as absence block + result.push(_absSeg) + } else { + const realJob = isAssist ? job._parentJob : job + const jLeft = (startH - H_START) * pxPerHr.value + const jWidth = Math.max(18, dur * pxPerHr.value) + result.push({ + type: isAssist ? 'assist' : 'job', job: realJob, + pinned: isPinned, pinnedTime: isPinned ? hToTime(startH) : null, isAssist, + assistPinned: isAssist ? isPinned : false, assistDur: isAssist ? dur : null, + assistNote: isAssist ? job._assistNote : null, assistTechId: isAssist ? tech.id : null, + style: { left: jLeft + 'px', width: jWidth + 'px', top: '6px', bottom: '6px', position: 'absolute' }, + }) } - const jLeft = (startH - H_START) * pxPerHr.value - const jWidth = Math.max(18, dur * pxPerHr.value) - result.push({ - type: isAssist ? 'assist' : 'job', job: realJob, - pinned: isPinned, pinnedTime: isPinned ? hToTime(startH) : null, isAssist, - assistPinned: isAssist ? isPinned : false, assistDur: isAssist ? dur : null, - assistNote: isAssist ? job._assistNote : null, assistTechId: isAssist ? tech.id : null, - style: { left: jLeft + 'px', width: jWidth + 'px', top: '6px', bottom: '6px', position: 'absolute' }, - }) prevEndH = startH + dur }) @@ -157,6 +225,7 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo }) }) + _daySegmentsCache.set(segKey, result) return result } @@ -168,11 +237,31 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo const assists = (tech.assistJobs || []) .filter(j => jobSpansDate(j, ds) && j.assistants.find(a => a.techId === tech.id)?.pinned) .map(j => ({ ...j, _isAssistChip: true, _assistDur: j.assistants.find(a => a.techId === tech.id)?.duration || j.duration })) - return { day: d, dateStr: ds, jobs: [...primary, ...assists] } + const absSegs = absenceSegmentsForDate(tech, ds) + return { day: d, dateStr: ds, jobs: [...primary, ...assists], absent: absSegs.length > 0, absenceInfo: absSegs[0] || null } }) } + // ── Memoization caches (cleared on reactive dependency changes) ────────── + const _periodLoadCache = new Map() + const _periodCapCache = new Map() + const _daySegmentsCache = new Map() + + // Invalidate caches when period/view changes + let _lastCacheKey = '' + function _checkCacheInvalidation () { + const key = `${currentView.value}||${periodStart.value}||${dayColumns.value.length}||${store.jobs.length}` + if (key !== _lastCacheKey) { + _lastCacheKey = key + _periodLoadCache.clear() + _periodCapCache.clear() + _daySegmentsCache.clear() + } + } + function periodLoadH (tech) { + _checkCacheInvalidation() + if (_periodLoadCache.has(tech.id)) return _periodLoadCache.get(tech.id) const dateSet = new Set(dayColumns.value.map(d => localDateStr(d))) let total = tech.queue.reduce((sum, j) => { const ds = getJobDate(j.id) @@ -185,9 +274,36 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo if (a?.pinned) total += parseFloat(a?.duration || j.duration) || 0 } }) + // Include absence hours as occupied time + dayColumns.value.forEach(d => { + const ds = localDateStr(d) + const absSegs = absenceSegmentsForDate(tech, ds) + absSegs.forEach(s => { total += s.endH - s.startH }) + }) + _periodLoadCache.set(tech.id, total) return total } + // Per-tech capacity for the visible period (for utilization bar denominator) + function techPeriodCapacityH (tech) { + _checkCacheInvalidation() + if (_periodCapCache.has(tech.id)) return _periodCapCache.get(tech.id) + let total = 0 + dayColumns.value.forEach(d => { + total += techDayCapacityH(tech, localDateStr(d)) + }) + const result = total || 8 // fallback to avoid /0 + _periodCapCache.set(tech.id, result) + return result + } + + // Per-tech end hour for current day (for capacity line position) + function techDayEndH (tech) { + const dayStr = localDateStr(periodStart.value) + const sched = techDaySchedule(tech, dayStr) + return sched ? sched.endH : 16 + } + function techsActiveOnDay (dateStr, resources) { return resources.filter(tech => tech.queue.some(j => jobSpansDate(j, dateStr)) || @@ -203,7 +319,8 @@ export function useScheduler (store, currentView, periodStart, periodDays, dayCo return { H_START, H_END, routeLegs, routeGeometry, - techAllJobsForDate, techDayJobsWithTravel, - techBookingsByDay, periodLoadH, techsActiveOnDay, dayJobCount, + techAllJobsForDate, techDayJobsWithTravel, absenceSegmentsForDate, + techBookingsByDay, periodLoadH, techPeriodCapacityH, techDayEndH, + techsActiveOnDay, dayJobCount, } } diff --git a/apps/ops/src/composables/useTechManagement.js b/apps/ops/src/composables/useTechManagement.js index 2dfea63..2d68767 100644 --- a/apps/ops/src/composables/useTechManagement.js +++ b/apps/ops/src/composables/useTechManagement.js @@ -1,5 +1,17 @@ import { ref } from 'vue' import { updateTech } from 'src/api/dispatch' +import { toErpStatus } from 'src/composables/useHelpers' + +const ABSENCE_REASONS = [ + { value: 'vacation', label: 'Vacances', icon: '🏖️' }, + { value: 'sick', label: 'Maladie', icon: '🤒' }, + { value: 'personal', label: 'Personnel', icon: '🏠' }, + { value: 'training', label: 'Formation', icon: '📚' }, + { value: 'injury', label: 'Blessure', icon: '🩹' }, + { value: 'other', label: 'Autre', icon: '📋' }, +] + +export { ABSENCE_REASONS } export function useTechManagement (store, invalidateRoutes) { const editingTech = ref(null) @@ -8,6 +20,12 @@ export function useTechManagement (store, invalidateRoutes) { const newTechDevice = ref('') const addingTech = ref(false) + // Absence modal state + const absenceModalOpen = ref(false) + const absenceModalTech = ref(null) + const absenceForm = ref({ reason: 'vacation', from: '', until: '', jobAction: 'unassign' }) + const absenceProcessing = ref(false) + async function saveTechField (tech, field, value) { const trimmed = typeof value === 'string' ? value.trim() : value if (field === 'full_name') { @@ -15,15 +33,17 @@ export function useTechManagement (store, invalidateRoutes) { tech.fullName = trimmed } else if (field === 'status') { tech.status = trimmed + tech.active = trimmed !== 'inactive' && trimmed !== 'off' } else if (field === 'phone') { if (trimmed === tech.phone) return tech.phone = trimmed } - try { await updateTech(tech.name || tech.id, { [field]: trimmed }) } + const saveValue = field === 'status' ? toErpStatus(trimmed) : trimmed + try { await updateTech(tech.name || tech.id, { [field]: saveValue }) } catch (_e) { /* save failed */ } } - async function addTech () { + async function addTech (extraFields = {}) { const name = newTechName.value.trim() if (!name || addingTech.value) return addingTech.value = true @@ -32,6 +52,7 @@ export function useTechManagement (store, invalidateRoutes) { full_name: name, phone: newTechPhone.value.trim() || '', traccar_device_id: newTechDevice.value || '', + ...extraFields, }) newTechName.value = '' newTechPhone.value = '' @@ -44,17 +65,141 @@ export function useTechManagement (store, invalidateRoutes) { finally { addingTech.value = false } } - async function removeTech (tech) { - if (!confirm(`Supprimer ${tech.fullName} ?`)) return + // ── Absence flow ── + + function openAbsenceModal (tech) { + absenceModalTech.value = tech + const today = new Date().toISOString().slice(0, 10) + absenceForm.value = { + reason: tech.absenceReason || 'vacation', + from: tech.absenceFrom || today, + until: tech.absenceUntil || '', + jobAction: 'unassign', // 'unassign' | 'reassign' + } + absenceModalOpen.value = true + } + + async function confirmAbsence () { + const tech = absenceModalTech.value + if (!tech) return + absenceProcessing.value = true + try { + const { reason, from, until, jobAction } = absenceForm.value + + // 1. Handle assigned jobs const linkedJobs = store.jobs.filter(j => j.assignedTech === tech.id) - for (const job of linkedJobs) { - await store.unassignJob(job.id) + if (linkedJobs.length > 0) { + if (jobAction === 'unassign') { + for (const job of linkedJobs) { + await store.unassignJob(job.id) + } + } + // 'reassign' is handled by the user manually after — jobs stay visible in unassigned pool } - await store.deleteTechnician(tech.id) + + // 2. Update tech status + absence fields + tech.status = 'off' + tech.active = false + tech.absenceReason = reason + tech.absenceFrom = from || null + tech.absenceUntil = until || null + tech.absenceStartTime = null + tech.absenceEndTime = null + + await updateTech(tech.name || tech.id, { + status: toErpStatus('off'), + absence_reason: reason, + absence_from: from || '', + absence_until: until || '', + absence_start_time: '', + absence_end_time: '', + }) + + absenceModalOpen.value = false } catch (e) { const msg = e?.message || String(e) - alert('Erreur suppression:\n' + msg.replace(/<[^>]+>/g, '')) + alert('Erreur: ' + msg.replace(/<[^>]+>/g, '')) + } + absenceProcessing.value = false + } + + async function endAbsence (tech) { + tech.status = 'available' + tech.active = true + tech.absenceReason = '' + tech.absenceFrom = null + tech.absenceUntil = null + tech.absenceStartTime = null + tech.absenceEndTime = null + + try { + await updateTech(tech.name || tech.id, { + status: toErpStatus('available'), + absence_reason: '', + absence_from: '', + absence_until: '', + absence_start_time: '', + absence_end_time: '', + }) + } catch (_e) { + tech.status = 'off' + tech.active = false + } + } + + // Permanent deactivation (non-dispatchable resource like support staff) + async function deactivateTech (tech) { + if (!confirm(`Désactiver ${tech.fullName} du dispatch ?\n\nLa ressource ne sera plus dispatchable mais reste dans le système.`)) return + try { + const linkedJobs = store.jobs.filter(j => j.assignedTech === tech.id) + for (const job of linkedJobs) await store.unassignJob(job.id) + tech.status = 'inactive' + tech.active = false + tech.absenceReason = '' + tech.absenceFrom = null + tech.absenceUntil = null + tech.absenceStartTime = null + tech.absenceEndTime = null + await updateTech(tech.name || tech.id, { status: toErpStatus('inactive'), absence_reason: '', absence_from: '', absence_until: '', absence_start_time: '', absence_end_time: '' }) + } catch (e) { + alert('Erreur: ' + (e?.message || String(e)).replace(/<[^>]+>/g, '')) + } + } + + async function reactivateTech (tech) { + tech.status = 'available' + tech.active = true + tech.absenceReason = '' + tech.absenceFrom = null + tech.absenceUntil = null + tech.absenceStartTime = null + tech.absenceEndTime = null + try { + await updateTech(tech.name || tech.id, { status: toErpStatus('available'), absence_reason: '', absence_from: '', absence_until: '', absence_start_time: '', absence_end_time: '' }) + } catch (_e) { + tech.status = 'inactive' + tech.active = false + } + } + + async function removeTech (tech) { + if (!confirm(`⚠ SUPPRIMER DÉFINITIVEMENT ${tech.fullName} ?\n\nCette action est irréversible.`)) return + try { + const linkedJobs = store.jobs.filter(j => j.assignedTech === tech.id) + for (const job of linkedJobs) await store.unassignJob(job.id) + await store.deleteTechnician(tech.id) + } catch (e) { + alert('Erreur: ' + (e?.message || String(e)).replace(/<[^>]+>/g, '')) + } + } + + async function saveWeeklySchedule (tech, schedule) { + tech.weeklySchedule = { ...schedule } + try { + await updateTech(tech.name || tech.id, { weekly_schedule: JSON.stringify(schedule) }) + } catch (e) { + alert('Erreur: ' + (e?.message || String(e)).replace(/<[^>]+>/g, '')) } } @@ -69,6 +214,10 @@ export function useTechManagement (store, invalidateRoutes) { return { editingTech, newTechName, newTechPhone, newTechDevice, addingTech, - saveTechField, addTech, removeTech, saveTraccarLink, + absenceModalOpen, absenceModalTech, absenceForm, absenceProcessing, + saveTechField, addTech, + openAbsenceModal, confirmAbsence, endAbsence, + deactivateTech, reactivateTech, removeTech, saveTraccarLink, saveWeeklySchedule, + ABSENCE_REASONS, } } diff --git a/apps/ops/src/composables/useUnifiedCreate.js b/apps/ops/src/composables/useUnifiedCreate.js new file mode 100644 index 0000000..64e7fd9 --- /dev/null +++ b/apps/ops/src/composables/useUnifiedCreate.js @@ -0,0 +1,320 @@ +import { reactive, ref, computed } from 'vue' +import { Notify } from 'quasar' +import { createDoc, listDocs } from 'src/api/erp' +import { createJob, fetchTags, createTag, updateTag, renameTag, deleteTag } from 'src/api/dispatch' + +const HUB_URL = window.location.hostname === 'localhost' ? 'http://localhost:3300' : 'https://msg.gigafibre.ca' + +/** + * Composable for unified ticket/task/work-order creation. + * + * @param {import('vue').Ref} mode - 'ticket' | 'task' | 'work-order' + * @param {Object} opts - Optional: { technicians, allTags, getTagColor } + */ +function nextWeekday () { + const d = new Date() + const day = d.getDay() + if (day === 6) d.setDate(d.getDate() + 2) // Sat → Mon + else if (day === 0) d.setDate(d.getDate() + 1) // Sun → Mon + return d.toISOString().slice(0, 10) +} + +export function useUnifiedCreate (mode, opts = {}) { + // ── Form state ──────────────────────────────────────────────────────────── + const form = reactive({ + subject: '', + priority: 'medium', + description: '', + issue_type: null, + service_location: null, + job_type: 'Autre', + duration_h: 1, + address: '', + assigned_tech: null, + scheduled_date: '', + depends_on: null, // parent dependency (ticket/task/job name) + tags: [], // [{ tag, level?, required? }] or ['label', ...] + _latitude: null, + _longitude: null, + _customer: '', + _source_issue: '', + _subscription: '', + }) + + const submitting = ref(false) + + // ── Field visibility by mode ────────────────────────────────────────────── + const fields = computed(() => { + const m = typeof mode === 'string' ? mode : mode.value + return { + showIssueType: m === 'ticket', + showServiceLocation: m === 'ticket', + showJobType: m === 'work-order', + showAddress: m === 'work-order', + showDuration: m === 'work-order' || m === 'task', + showTags: m === 'work-order' || m === 'task', + showTech: m === 'work-order', + showScheduledDate: m === 'work-order' || m === 'task', + showParent: true, + } + }) + + // ── Tags (standalone mode — used when not injected from dispatch) ───────── + const internalTags = ref([]) + const tagsLoaded = ref(false) + + async function loadTags () { + if (tagsLoaded.value) return + try { + internalTags.value = await fetchTags() + tagsLoaded.value = true + } catch { /* tags optional */ } + } + + function allTags () { + return opts.allTags?.value || opts.allTags || internalTags.value + } + + function getTagColor (label) { + if (opts.getTagColor) return opts.getTagColor(label) + const t = allTags().find(t => t.label === label || t.name === label) + return t?.color || '#6b7280' + } + + async function onCreateTag ({ label, color }) { + try { + const doc = await createTag(label, 'Custom', color) + internalTags.value.push({ name: doc.name || label, label, color, category: 'Custom' }) + } catch (e) { + Notify.create({ type: 'negative', message: 'Erreur création tag: ' + e.message }) + } + } + + async function onUpdateTag ({ name, color }) { + try { + await updateTag(name, { color }) + const t = internalTags.value.find(t => t.name === name || t.label === name) + if (t) t.color = color + } catch { /* silent */ } + } + + async function onRenameTag ({ oldName, newName }) { + try { + await renameTag(oldName, newName) + const t = internalTags.value.find(t => t.name === oldName || t.label === oldName) + if (t) { t.name = newName; t.label = newName } + } catch { /* silent */ } + } + + async function onDeleteTag (label) { + try { + await deleteTag(label) + internalTags.value = internalTags.value.filter(t => t.label !== label && t.name !== label) + } catch { /* silent */ } + } + + // ── Parent dependency search ────────────────────────────────────────────── + const parentOptions = ref([]) + let parentDebounce = null + let recentLoaded = false + + // Load recent tickets on first open (no query needed) + async function loadRecentParents () { + if (recentLoaded && parentOptions.value.length) return + try { + const issues = await listDocs('Issue', { + fields: ['name', 'subject', 'status'], + limit: 15, + orderBy: 'creation desc', + }) + parentOptions.value = issues.map(i => ({ + label: `${i.name}: ${i.subject || ''}`, + value: i.name, + type: 'Issue', + status: i.status, + })) + recentLoaded = true + } catch { /* silent */ } + } + + function searchParent (query, done) { + clearTimeout(parentDebounce) + // No query → show recent tickets + if (!query || query.length < 2) { + loadRecentParents().then(() => done?.(parentOptions.value)) + return + } + parentDebounce = setTimeout(async () => { + try { + const [issues, tasks] = await Promise.all([ + listDocs('Issue', { + or_filters: { subject: ['like', `%${query}%`], name: ['like', `%${query}%`] }, + fields: ['name', 'subject', 'status'], + limit: 10, + orderBy: 'creation desc', + }), + listDocs('Task', { + or_filters: { subject: ['like', `%${query}%`], name: ['like', `%${query}%`] }, + fields: ['name', 'subject', 'status'], + limit: 10, + orderBy: 'creation desc', + }), + ]) + const results = [ + ...issues.map(i => ({ label: `${i.name}: ${i.subject || ''}`, value: i.name, type: 'Issue', status: i.status })), + ...tasks.map(t => ({ label: `${t.name}: ${t.subject || ''}`, value: t.name, type: 'Task', status: t.status })), + ] + parentOptions.value = results + done?.(results) + } catch { done?.([]) } + }, 300) + } + + // ── Best tech ranking ───────────────────────────────────────────────────── + const findingBest = ref(false) + const ranking = ref([]) + + async function findBestTech () { + findingBest.value = true + try { + const res = await fetch(HUB_URL + '/dispatch/best-tech', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + latitude: form._latitude, + longitude: form._longitude, + duration_h: form.duration_h, + date: form.scheduled_date || new Date().toISOString().slice(0, 10), + }), + }) + const data = await res.json() + ranking.value = data.ranking || [] + } catch (e) { + Notify.create({ type: 'negative', message: 'Erreur calcul optimal' }) + } finally { + findingBest.value = false + } + } + + // ── Reset form from context ─────────────────────────────────────────────── + function resetForm (ctx = {}) { + form.subject = ctx.subject || '' + form.priority = ctx.priority || 'medium' + form.description = ctx.description || '' + form.issue_type = ctx.issue_type || null + form.service_location = ctx.service_location || null + form.job_type = ctx.job_type || 'Autre' + form.duration_h = ctx.duration_h || 1 + form.address = ctx.address || '' + form.assigned_tech = ctx.assigned_tech || null + form.scheduled_date = ctx.scheduled_date || '' + form.depends_on = ctx.depends_on || null + form.tags = ctx.tags || [] + form._latitude = ctx.latitude || null + form._longitude = ctx.longitude || null + form._customer = ctx.customer || '' + form._source_issue = ctx.source_issue || '' + form._subscription = ctx.subscription || '' + ranking.value = [] + } + + // ── Submit — routes to correct backend ──────────────────────────────────── + async function submit () { + if (!form.subject?.trim()) { + Notify.create({ type: 'warning', message: 'Le sujet est requis' }) + return null + } + submitting.value = true + try { + const m = typeof mode === 'string' ? mode : mode.value + let result + + if (m === 'ticket') { + const data = { + customer: form._customer, + subject: form.subject.trim(), + priority: capitalize(form.priority), + status: 'Open', + description: form.description || '', + } + if (form.issue_type) data.issue_type = form.issue_type + if (form.service_location) data.service_location = form.service_location + if (form.depends_on) data.depends_on = form.depends_on + result = await createDoc('Issue', data) + Notify.create({ type: 'positive', message: 'Ticket créé' }) + + } else if (m === 'task') { + const data = { + subject: form.subject.trim(), + priority: capitalize(form.priority), + status: 'Open', + description: form.description || '', + } + if (form.duration_h) data.expected_time = form.duration_h + if (form.scheduled_date) data.exp_start_date = form.scheduled_date + if (form.depends_on) data.parent_task = form.depends_on + result = await createDoc('Task', data) + Notify.create({ type: 'positive', message: 'Tâche créée' }) + + } else if (m === 'work-order') { + const ticketId = 'DJ-' + Date.now().toString(36).toUpperCase() + const payload = { + ticket_id: ticketId, + subject: form.subject.trim(), + address: form.address, + duration_h: form.duration_h, + priority: form.priority, + status: form.assigned_tech ? 'assigned' : 'open', + job_type: form.job_type, + source_issue: form._source_issue, + customer: form._customer, + service_location: form.service_location || '', + depends_on: form.depends_on || '', + scheduled_date: form.scheduled_date || (form.assigned_tech ? nextWeekday() : ''), + notes: form.description || '', + assigned_tech: form.assigned_tech || '', + latitude: form._latitude || '', + longitude: form._longitude || '', + tags: form.tags.map(t => typeof t === 'string' ? t : t.tag).join(','), + } + result = await createJob(payload) + Notify.create({ type: 'positive', message: form.assigned_tech ? 'Travail créé et assigné' : 'Travail créé' }) + } + + return result + } catch (err) { + Notify.create({ type: 'negative', message: `Erreur: ${err.message || err}` }) + return null + } finally { + submitting.value = false + } + } + + return { + form, + fields, + submitting, + resetForm, + submit, + // Tags + loadTags, + allTagsList: computed(() => allTags()), + getTagColor, + onCreateTag, + onUpdateTag, + onRenameTag, + onDeleteTag, + // Parent + parentOptions, + searchParent, + // Best tech + findingBest, + ranking, + findBestTech, + } +} + +function capitalize (s) { + if (!s) return s + return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() +} diff --git a/apps/ops/src/config/nav.js b/apps/ops/src/config/nav.js index 5fcf9e9..d1efb58 100644 --- a/apps/ops/src/config/nav.js +++ b/apps/ops/src/config/nav.js @@ -1,14 +1,13 @@ // Ops sidebar navigation + search filter options +// `requires` = capability needed to see this nav item (null = always visible) export const navItems = [ - { path: '/', icon: 'LayoutDashboard', label: 'Tableau de bord' }, - { path: '/clients', icon: 'Users', label: 'Clients' }, - { path: '/dispatch', icon: 'Truck', label: 'Dispatch' }, - { path: '/tickets', icon: 'Ticket', label: 'Tickets' }, - { path: '/equipe', icon: 'UsersRound', label: 'Équipe' }, - { path: '/rapports', icon: 'BarChart3', label: 'Rapports' }, - { path: '/ocr', icon: 'ScanText', label: 'OCR Factures' }, - { path: '/telephony', icon: 'Phone', label: 'Téléphonie' }, - { path: '/settings', icon: 'Settings', label: 'Paramètres' }, + { path: '/', icon: 'LayoutDashboard', label: 'Tableau de bord', requires: 'view_dashboard_kpi' }, + { path: '/clients', icon: 'Users', label: 'Clients', requires: 'view_clients' }, + { path: '/dispatch', icon: 'Truck', label: 'Dispatch', requires: 'view_all_jobs' }, + { path: '/tickets', icon: 'Ticket', label: 'Tickets', requires: 'view_all_tickets' }, + { path: '/equipe', icon: 'UsersRound', label: 'Équipe', requires: 'manage_users' }, + { path: '/rapports', icon: 'BarChart3', label: 'Rapports', requires: 'view_dashboard_kpi' }, + { path: '/settings', icon: 'Settings', label: 'Paramètres', requires: 'view_settings' }, ] export const territoryOptions = [ diff --git a/apps/ops/src/layouts/MainLayout.vue b/apps/ops/src/layouts/MainLayout.vue index 3b91b75..0be0fae 100644 --- a/apps/ops/src/layouts/MainLayout.vue +++ b/apps/ops/src/layouts/MainLayout.vue @@ -29,8 +29,8 @@ - {{ auth.user || 'User' }} - {{ auth.user || 'Déconnexion' }} + {{ userName || auth.user || 'User' }} + {{ userName || auth.user || 'Déconnexion' }}
@@ -97,6 +97,17 @@ + + + + + + + + Conversations + + + @@ -104,16 +115,28 @@ import { ref, computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useAuthStore } from 'src/stores/auth' +import { usePermissions } from 'src/composables/usePermissions' import { listDocs } from 'src/api/erp' -import { navItems } from 'src/config/nav' +import { navItems as allNavItems } from 'src/config/nav' import { LayoutDashboard, Users, Truck, Ticket, UsersRound, BarChart3, - ScanText, Phone, Settings, LogOut, PanelLeftOpen, PanelLeftClose, + Settings, LogOut, PanelLeftOpen, PanelLeftClose, } from 'lucide-vue-next' +import ConversationPanel from 'src/components/shared/ConversationPanel.vue' +import { useConversations } from 'src/composables/useConversations' -const icons = { LayoutDashboard, Users, Truck, Ticket, UsersRound, BarChart3, ScanText, Phone, Settings, LogOut, PanelLeftOpen, PanelLeftClose } +const icons = { LayoutDashboard, Users, Truck, Ticket, UsersRound, BarChart3, Settings, LogOut, PanelLeftOpen, PanelLeftClose } + +const { panelOpen, activeCount: convCount } = useConversations() +function toggleConvPanel () { panelOpen.value = !panelOpen.value } const auth = useAuthStore() +const { can, isLoaded, userName } = usePermissions() + +// Filter nav items based on user capabilities +const navItems = computed(() => + allNavItems.filter(n => !n.requires || can(n.requires)) +) const route = useRoute() const router = useRouter() const drawer = ref(true) @@ -122,7 +145,7 @@ const collapsed = ref(localStorage.getItem('ops-sidebar-collapsed') !== 'false') const sidebarW = computed(() => collapsed.value ? 64 : 220) const isDispatch = computed(() => route.path === '/dispatch') const currentNav = computed(() => - navItems.find(n => n.path === route.path) || navItems.find(n => route.path.startsWith(n.path) && n.path !== '/') + navItems.value.find(n => n.path === route.path) || navItems.value.find(n => route.path.startsWith(n.path) && n.path !== '/') ) function isActive (path) { diff --git a/apps/ops/src/modules/dispatch/components/MonthCalendar.vue b/apps/ops/src/modules/dispatch/components/MonthCalendar.vue index 19b50d9..7a20f7f 100644 --- a/apps/ops/src/modules/dispatch/components/MonthCalendar.vue +++ b/apps/ops/src/modules/dispatch/components/MonthCalendar.vue @@ -1,6 +1,6 @@