Files
shopdb-flask/frontend/src/views/Dashboard.vue
cproudlock c34815b87e
Some checks failed
CI / backend (push) Failing after 8s
CI / naming (push) Successful in 2s
CI / frontend (push) Successful in 9s
CI / migrations-mysql (push) Failing after 7s
dashboard: overflow links somewhere, tiles say what they count, rows stay inside
Four fixes, all from looking at the real board.

"and N more" now links to a page showing them all. Telling someone 35 more PCs
are silent and leaving them to find the list is worse than not saying it. Each
card names its own destination and a test checks it against the routes that
actually exist - a viewall pointing at a route nobody wrote is the same rot the
endpoint check already guards, just failing in the browser instead of the API.

PRINTER ROWS ESCAPED THE CARD. A flex child will not shrink below its content
width unless told to, so text-overflow never engaged and a row carrying three
cartridge readings plus a location simply ran past the border. min-width:0 on
the row parts is what enables the ellipsis; meta shrinks first because it
matters least, and the card clips as a backstop.

THE STAT TILES WERE INCOHERENT. Two counted asset TYPES, two counted asset
STATUSES, and nothing said which - with the status one labelled "Active", which
reads as "not deleted" but meant status = In Use across every type. Each tile
now counts one thing and its label says so.

PCs GONE SILENT IS NARROWER, and better for it. A PC that never reported at all
is usually a hand-made or imported record rather than a bay that broke, and a
PC that is not In Use is silent ON PURPOSE - that is the status doing its job.
Both were burying the real signal: a machine that was working, is not now, and
nobody has marked as anything else.
2026-08-11 15:28:29 -04:00

154 lines
5.3 KiB
Vue

<template>
<div>
<div class="page-header">
<h2>Dashboard</h2>
</div>
<div v-if="loading" class="loading">Loading...</div>
<template v-else>
<!-- What needs a person, before the totals that are true every day.
Cards are declared by plugins (get_dashboard_widgets) and rendered
generically; an empty or failed card hides itself. See
docs/proposals/dashboard-live-fleet.md. -->
<DashboardCards />
<!-- Inventory context, below the exceptions.
These used to mix two different things without saying so: two tiles
counted asset TYPES and two counted asset STATUSES, and the status
one was labelled "Active", which reads as "not deleted" when it
actually meant status = In Use across every type. Now every tile
counts one thing and its label says which. -->
<div class="dashboard-grid">
<div class="stat-card">
<div class="label">Machines</div>
<div class="value">{{ stats.totalmachines || 0 }}</div>
</div>
<div class="stat-card">
<div class="label">PCs</div>
<div class="value">{{ stats.totalpc || 0 }}</div>
</div>
<div class="stat-card">
<div class="label">Printers</div>
<div class="value">{{ stats.totalprinter || 0 }}</div>
</div>
<div class="stat-card">
<div class="label">Network devices</div>
<div class="value">{{ stats.totalnetwork || 0 }}</div>
</div>
<div class="stat-card">
<div class="label">All assets</div>
<div class="value">{{ stats.totalassets || 0 }}</div>
</div>
<div class="stat-card success">
<div class="label">All assets in use</div>
<div class="value">{{ stats.activeassets || 0 }}</div>
</div>
<div class="stat-card warning">
<div class="label">All assets in repair</div>
<div class="value">{{ stats.inrepair || 0 }}</div>
</div>
</div>
<!-- Printer Stats -->
<div class="card">
<div class="card-header">
<h3>Printers</h3>
<router-link to="/printers" class="btn btn-secondary btn-sm">View All</router-link>
</div>
<div class="dashboard-grid">
<div class="stat-card">
<div class="label">Total Printers</div>
<div class="value">{{ printerStats.totalprinters || 0 }}</div>
</div>
<div class="stat-card success">
<div class="label">Online</div>
<div class="value">{{ printerStats.online || 0 }}</div>
</div>
<div class="stat-card warning">
<div class="label">Low Supplies</div>
<div class="value">{{ printerStats.lowsupplies || 0 }}</div>
</div>
<div class="stat-card danger">
<div class="label">Critical</div>
<div class="value">{{ printerStats.criticalsupplies || 0 }}</div>
</div>
</div>
</div>
<!-- Recent Devices -->
<div class="card">
<div class="card-header">
<h3>Recent Devices</h3>
<router-link to="/machines" class="btn btn-secondary btn-sm">View All</router-link>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Asset #</th>
<th>Type</th>
<th>Status</th>
<th>Business Unit</th>
</tr>
</thead>
<tbody>
<tr v-for="machine in recentMachines" :key="machine.assetid">
<td>{{ machine.assetnumber || machine.name || '-' }}</td>
<td>{{ machine.assettypename || '-' }}</td>
<td>{{ machine.statusname || '-' }}</td>
<td>{{ machine.businessunitname || '-' }}</td>
</tr>
<tr v-if="recentMachines.length === 0">
<td colspan="4" style="text-align: center; color: var(--text-light);">
No devices found
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
</div>
</template>
<script setup>
import DashboardCards from '@/components/DashboardCards.vue'
import { ref, onMounted } from 'vue'
import { dashboardApi, assetsApi, printersApi } from '../api'
const loading = ref(true)
const stats = ref({})
const printerStats = ref({})
const recentMachines = ref([])
onMounted(async () => {
try {
const [dashRes, machinesRes, printersRes] = await Promise.all([
dashboardApi.summary().catch(() => ({ data: { data: {} } })),
assetsApi.list({ perpage: 5 }).catch(() => ({ data: { data: [] } })),
printersApi.dashboardSummary().catch(() => ({ data: { data: {} } }))
])
stats.value = dashRes.data.data || {}
recentMachines.value = machinesRes.data.data || []
printerStats.value = printersRes.data.data || {}
} catch (error) {
console.error('Dashboard load error:', error)
} finally {
loading.value = false
}
})
function getStatusClass(status) {
if (!status) return 'badge-info'
const s = status.toLowerCase()
if (s === 'in use' || s === 'active') return 'badge-success'
if (s === 'in repair') return 'badge-warning'
if (s === 'retired') return 'badge-danger'
return 'badge-info'
}
</script>