Add print badges, pagination, route splitting, JWT auth fixes, and list page alignment
- Fix equipment badge barcode not rendering (loading race condition) - Fix printer QR code not rendering on initial load (same race condition) - Add model image to equipment badge via imageurl from Model table - Fix white-on-white machine number text on badge, tighten barcode spacing - Add PaginationBar component used across all list pages - Split monolithic router into per-plugin route modules - Fix 25 GET API endpoints returning 401 (jwt_required -> optional=True) - Align list page columns across Equipment, PCs, and Network pages - Add print views: EquipmentBadge, PrinterQRSingle, PrinterQRBatch, USBLabelBatch - Add PC Relationships report, migration docs, and CLAUDE.md project guide - Various plugin model, API, and frontend refinements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,22 +16,13 @@
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<router-link to="/">Dashboard</router-link>
|
||||
<router-link to="/calendar">Calendar</router-link>
|
||||
<router-link to="/map">Map</router-link>
|
||||
|
||||
<div class="nav-section">Assets</div>
|
||||
<router-link to="/machines">Equipment</router-link>
|
||||
<router-link to="/pcs">PCs</router-link>
|
||||
<router-link to="/printers">Printers</router-link>
|
||||
<router-link to="/network">Network Devices</router-link>
|
||||
<router-link to="/usb">USB Devices</router-link>
|
||||
|
||||
<div class="nav-section">Information</div>
|
||||
<router-link to="/applications">Applications</router-link>
|
||||
<router-link to="/knowledgebase">Knowledge Base</router-link>
|
||||
<router-link to="/notifications">Notifications</router-link>
|
||||
<router-link to="/reports">Reports</router-link>
|
||||
<template v-for="item in navItems" :key="item.route">
|
||||
<div v-if="item.section" class="nav-section">{{ item.section }}</div>
|
||||
<router-link :to="item.route">
|
||||
<component v-if="item.iconComponent" :is="item.iconComponent" :size="16" />
|
||||
{{ item.name }}
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<div class="nav-section">Displays</div>
|
||||
<a href="/shopfloor" target="_blank" class="external-link">Shopfloor Dashboard</a>
|
||||
@@ -42,8 +33,8 @@
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<button class="theme-toggle" @click="toggleTheme" :title="currentTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'">
|
||||
<span v-if="currentTheme === 'dark'">☀️ Light</span>
|
||||
<span v-else>🌙 Dark</span>
|
||||
<span v-if="currentTheme === 'dark'"><Sun :size="14" /> Light</span>
|
||||
<span v-else><Moon :size="14" /> Dark</span>
|
||||
</button>
|
||||
|
||||
<div class="user-menu">
|
||||
@@ -63,15 +54,92 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
Sun, Moon, LayoutDashboard, Calendar, Map, Cog, Monitor,
|
||||
Printer, Globe, Usb, AppWindow, BookOpen, BarChart3, Bell
|
||||
} from 'lucide-vue-next'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { currentTheme, toggleTheme } from '../stores/theme'
|
||||
import { dashboardApi } from '../api'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const navItems = ref([])
|
||||
|
||||
// Map backend icon names to Lucide components
|
||||
const iconMap = {
|
||||
'layout-dashboard': LayoutDashboard,
|
||||
'calendar': Calendar,
|
||||
'map': Map,
|
||||
'cog': Cog,
|
||||
'desktop': Monitor,
|
||||
'printer': Printer,
|
||||
'network-wired': Globe,
|
||||
'usb': Usb,
|
||||
'bell': Bell,
|
||||
'app-window': AppWindow,
|
||||
'book-open': BookOpen,
|
||||
'bar-chart-3': BarChart3,
|
||||
}
|
||||
|
||||
// Default navigation (used as fallback if API fails)
|
||||
const defaultNav = [
|
||||
{ name: 'Dashboard', icon: 'layout-dashboard', route: '/', position: 0 },
|
||||
{ name: 'Notifications', icon: 'bell', route: '/notifications', position: 5 },
|
||||
{ name: 'Calendar', icon: 'calendar', route: '/calendar', position: 6 },
|
||||
{ name: 'Map', icon: 'map', route: '/map', position: 4 },
|
||||
{ name: 'Equipment', icon: 'cog', route: '/machines', position: 10 },
|
||||
{ name: 'PCs', icon: 'desktop', route: '/pcs', position: 15 },
|
||||
{ name: 'Network', icon: 'network-wired', route: '/network', position: 18 },
|
||||
{ name: 'Printers', icon: 'printer', route: '/printers', position: 20 },
|
||||
{ name: 'USB Devices', icon: 'usb', route: '/usb', position: 45 },
|
||||
{ name: 'Applications', icon: 'app-window', route: '/applications', position: 30, section: 'information' },
|
||||
{ name: 'Knowledge Base', icon: 'book-open', route: '/knowledgebase', position: 35 },
|
||||
{ name: 'Reports', icon: 'bar-chart-3', route: '/reports', position: 40 },
|
||||
]
|
||||
|
||||
function buildNavItems(items) {
|
||||
// Sort by position
|
||||
const sorted = [...items].sort((a, b) => (a.position || 99) - (b.position || 99))
|
||||
|
||||
// Assign section headers based on position ranges
|
||||
const result = []
|
||||
let currentSection = null
|
||||
|
||||
for (const item of sorted) {
|
||||
let section = null
|
||||
if (item.position >= 10 && item.position < 30 && currentSection !== 'Assets') {
|
||||
section = 'Assets'
|
||||
currentSection = 'Assets'
|
||||
} else if (item.section === 'information' || (item.position >= 30 && item.position < 50 && currentSection !== 'Information')) {
|
||||
if (currentSection !== 'Information') {
|
||||
section = 'Information'
|
||||
currentSection = 'Information'
|
||||
}
|
||||
}
|
||||
|
||||
result.push({
|
||||
...item,
|
||||
section,
|
||||
iconComponent: iconMap[item.icon] || null
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await dashboardApi.navigation()
|
||||
navItems.value = buildNavItems(response.data.data || [])
|
||||
} catch (err) {
|
||||
// Fall back to default navigation if API unavailable
|
||||
navItems.value = buildNavItems(defaultNav)
|
||||
}
|
||||
})
|
||||
|
||||
function performSearch() {
|
||||
if (searchQuery.value.trim()) {
|
||||
|
||||
Reference in New Issue
Block a user