gigafibre-fsm/apps/client/src/pages/DashboardPage.vue
louispaulb 607ea54b5c refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00

117 lines
4.1 KiB
Vue

<template>
<q-page padding>
<div class="page-title">Bonjour, {{ store.customerName }}</div>
<div class="row q-col-gutter-md">
<!-- Unpaid invoices -->
<div class="col-12 col-sm-6 col-md-4">
<div class="portal-card">
<div class="text-caption text-grey-7">Factures impayees</div>
<div class="summary-value">{{ unpaidCount }}</div>
<div v-if="unpaidTotal > 0" class="text-body2 text-grey-7">
{{ formatMoney(unpaidTotal) }} a payer
</div>
<div class="row q-gutter-sm q-mt-sm">
<q-btn flat color="primary" label="Voir les factures" to="/invoices" no-caps />
<q-btn v-if="unpaidTotal > 0" unelevated color="indigo" label="Payer" icon="payment"
no-caps size="sm" :loading="payLoading" @click="payBalance" />
</div>
</div>
</div>
<!-- Open tickets -->
<div class="col-12 col-sm-6 col-md-4">
<div class="portal-card">
<div class="text-caption text-grey-7">Tickets ouverts</div>
<div class="summary-value">{{ openTickets }}</div>
<q-btn flat color="primary" label="Voir le support" to="/tickets" class="q-mt-sm" no-caps />
</div>
</div>
<!-- Quick action -->
<div class="col-12 col-sm-6 col-md-4">
<div class="portal-card">
<div class="text-caption text-grey-7">Besoin d'aide?</div>
<div class="text-h6 q-mt-xs">Contactez-nous</div>
<q-btn flat color="primary" label="Nouveau ticket" to="/tickets" class="q-mt-sm" no-caps icon="add" />
</div>
</div>
</div>
<!-- Recent invoices -->
<div v-if="recentInvoices.length" class="q-mt-lg">
<div class="text-subtitle1 text-weight-medium q-mb-sm">Dernières factures</div>
<q-list bordered separator class="rounded-borders bg-white">
<q-item v-for="inv in recentInvoices" :key="inv.name" clickable @click="$router.push('/invoices/' + inv.name)">
<q-item-section>
<q-item-label>{{ inv.name }}</q-item-label>
<q-item-label caption>{{ formatDate(inv.posting_date) }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label>{{ formatMoney(inv.grand_total) }}</q-item-label>
<q-item-label caption :class="statusClass(inv.status)">
{{ inv.status }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</q-page>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useQuasar } from 'quasar'
import { useCustomerStore } from 'src/stores/customer'
import { fetchInvoices, fetchTickets } from 'src/api/portal'
import { checkoutBalance } from 'src/api/payments'
import { useFormatters } from 'src/composables/useFormatters'
const $q = useQuasar()
const store = useCustomerStore()
const { formatDate, formatMoney } = useFormatters()
const recentInvoices = ref([])
const allTickets = ref([])
const payLoading = ref(false)
const unpaidCount = computed(() =>
recentInvoices.value.filter(i => i.outstanding_amount > 0).length,
)
const unpaidTotal = computed(() =>
recentInvoices.value.filter(i => i.outstanding_amount > 0)
.reduce((sum, i) => sum + i.outstanding_amount, 0),
)
const openTickets = computed(() =>
allTickets.value.filter(t => t.status === 'Open').length,
)
function statusClass (status) {
if (status === 'Paid') return 'status-paid'
if (status === 'Unpaid' || status === 'Overdue') return 'status-unpaid'
return ''
}
async function payBalance () {
payLoading.value = true
try {
const result = await checkoutBalance(store.customerId)
if (result.url) window.location.href = result.url
} catch (e) {
$q.notify({ message: 'Erreur: ' + (e.message || 'Paiement impossible'), color: 'negative' })
} finally {
payLoading.value = false
}
}
onMounted(async () => {
if (!store.customerId) return
const [invoices, tickets] = await Promise.all([
fetchInvoices(store.customerId, { pageSize: 5 }),
fetchTickets(store.customerId),
])
recentInvoices.value = invoices
allTickets.value = tickets
})
</script>