- Remove apps/dispatch/ (100% replaced by ops dispatch module, unmaintained) - Commit services/targo-hub/lib/ (24 modules, 6290 lines — was never tracked) - Commit services/docuseal + services/legacy-db docker-compose configs - Extract client app composables: useOTP, useAddressSearch, catalog data, format utils - Refactor CartPage.vue 630→175 lines, CatalogPage.vue 375→95 lines - Clean hardcoded credentials from config.js fallback values - Add client portal: catalog, cart, checkout, OTP verification, address search - Add ops: NetworkPage, AgentFlowsPage, ConversationPanel, UnifiedCreateModal - Add ops composables: useBestTech, useConversations, usePermissions, useScanner - Add field app: scanner composable, docker/nginx configs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.3 KiB
JavaScript
99 lines
2.3 KiB
JavaScript
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,
|
|
}
|
|
})
|