372 lines
8.4 KiB
Vue
372 lines
8.4 KiB
Vue
<template>
|
|
<div class="dashboard">
|
|
<h1 class="dashboard-title">Dashboard</h1>
|
|
|
|
<div class="widgets-grid">
|
|
<!-- Calendar Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<Calendar class="widget-icon" />
|
|
<h3>Service Calendar</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="metric">
|
|
<span class="metric-number">8</span>
|
|
<span class="metric-label">Services Scheduled Today</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-number">15</span>
|
|
<span class="metric-label">Services This Week</span>
|
|
</div>
|
|
<Button
|
|
label="View Calendar"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/calendar')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- Clients Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<Community class="widget-icon" />
|
|
<h3>Client Contact List</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="status-row">
|
|
<Tag severity="success" value="5 Active Jobs" />
|
|
<Tag severity="warning" value="3 Pending Estimates" />
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-number">{{ clientData.length }}</span>
|
|
<span class="metric-label">Total Client Records</span>
|
|
</div>
|
|
<Button
|
|
label="View Client List"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/clients')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- Jobs Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<Hammer class="widget-icon" />
|
|
<h3>Job Management</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="status-row">
|
|
<Tag severity="info" value="4 In Progress" />
|
|
<Tag severity="success" value="2 Completed" />
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-number">{{
|
|
jobData.filter((job) => job.overAllStatus === "in progress").length
|
|
}}</span>
|
|
<span class="metric-label">Jobs In Progress</span>
|
|
</div>
|
|
<Button
|
|
label="View All Jobs"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/jobs')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- Routes Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<PathArrowSolid class="widget-icon" />
|
|
<h3>Service Routes</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="metric">
|
|
<span class="metric-number">6</span>
|
|
<span class="metric-label">Active Routes Today</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-number">45.2</span>
|
|
<span class="metric-label">Avg. Miles per Route</span>
|
|
</div>
|
|
<Button
|
|
label="View Routes"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/routes')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- Time Sheets Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<Clock class="widget-icon" />
|
|
<h3>Employee Timesheets</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="metric">
|
|
<span class="metric-number">32.5</span>
|
|
<span class="metric-label">Total Hours This Week</span>
|
|
</div>
|
|
<div class="status-row">
|
|
<Tag severity="success" value="5 Approved" />
|
|
<Tag severity="warning" value="2 Pending Approval" />
|
|
</div>
|
|
<Button
|
|
label="View Timesheets"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/timesheets')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
|
|
<!-- Warranties Widget -->
|
|
<Card class="widget-card">
|
|
<template #header>
|
|
<div class="widget-header">
|
|
<HistoricShield class="widget-icon" />
|
|
<h3>Warranty Claims</h3>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="widget-content">
|
|
<div class="metric">
|
|
<span class="metric-number">10</span>
|
|
<span class="metric-label">Open Claims</span>
|
|
</div>
|
|
<div class="status-row">
|
|
<Tag severity="success" value="3 Completed" />
|
|
<Tag severity="warning" value="4 In Progress" />
|
|
</div>
|
|
<Button
|
|
label="View Claims"
|
|
size="small"
|
|
outlined
|
|
@click="navigateTo('/warranties')"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
</div>
|
|
|
|
<!-- Quick Stats Summary -->
|
|
<div class="summary-section">
|
|
<Card>
|
|
<template #header>
|
|
<h3>Quick Stats</h3>
|
|
</template>
|
|
<template #content>
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ totalRevenue }}</span>
|
|
<span class="stat-label">Monthly Revenue</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ completedJobs }}</span>
|
|
<span class="stat-label">Jobs Completed</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ clientSatisfaction }}%</span>
|
|
<span class="stat-label">Client Satisfaction</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value">{{ avgResponseTime }}h</span>
|
|
<span class="stat-label">Avg Response Time</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from "vue";
|
|
import { useRouter } from "vue-router";
|
|
import Card from "primevue/card";
|
|
import Button from "primevue/button";
|
|
import Tag from "primevue/tag";
|
|
import { Calendar, Community, Hammer, PathArrowSolid, Clock, HistoricShield } from "@iconoir/vue";
|
|
import DataUtils from "../../utils.js";
|
|
import { useNotificationStore } from "../../stores/notifications-primevue";
|
|
|
|
const router = useRouter();
|
|
|
|
// Dummy data from utils
|
|
const clientData = ref(DataUtils.dummyClientData);
|
|
const jobData = ref(DataUtils.dummyJobData);
|
|
const notifications = useNotificationStore();
|
|
|
|
// Computed values for dashboard metrics
|
|
const totalRevenue = computed(() => "$47,250");
|
|
const completedJobs = computed(
|
|
() => jobData.value.filter((job) => job.overAllStatus === "completed").length,
|
|
);
|
|
const clientSatisfaction = computed(() => 94);
|
|
const avgResponseTime = computed(() => 2.3);
|
|
|
|
const navigateTo = (path) => {
|
|
router.push(path);
|
|
};
|
|
onMounted(() => {
|
|
notifications.addWarning("Dashboard metrics are based on dummy data for demonstration purposes. UPDATES COMING SOON!");
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dashboard {
|
|
padding: 20px;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.dashboard-title {
|
|
color: #2c3e50;
|
|
margin-bottom: 30px;
|
|
font-size: 2.5rem;
|
|
font-weight: 300;
|
|
}
|
|
|
|
.widgets-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.widget-card {
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
border-radius: 12px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
transition:
|
|
transform 0.2s ease,
|
|
box-shadow 0.2s ease;
|
|
}
|
|
|
|
.widget-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.widget-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 20px 20px 0;
|
|
}
|
|
|
|
.widget-icon {
|
|
color: rgb(69, 112, 101);
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.widget-header h3 {
|
|
margin: 0;
|
|
color: #2c3e50;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.widget-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.metric {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
}
|
|
|
|
.metric-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: rgb(69, 112, 101);
|
|
}
|
|
|
|
.metric-label {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.status-row {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.summary-section {
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
}
|
|
|
|
.stat-value {
|
|
display: block;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: rgb(69, 112, 101);
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.stat-label {
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.widgets-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.dashboard-title {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
</style>
|