custom_ui/frontend/src/components/pages/Home.vue
2026-01-24 17:10:42 -05:00

525 lines
13 KiB
Vue

<template>
<div class="dashboard">
<h1 class="dashboard-title">Today's Tasks</h1>
<div class="widgets-grid">
<!-- Locates Widget -->
<Card>
<template #header>
<div class="widget-header">
<ShieldSearch class="widget-icon" />
<h3>Locates</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Locates"
:categories="chartData.locates"
>
</TodoChart>
<button class="sidebar-button" @click="navigateTo('/tasks?subject=Locate')">
View Locates
</button>
</div>
</template>
</Card>
<!-- Permits Widget -->
<Card >
<template #header>
<div class="widget-header">
<ClipboardCheck class="widget-icon" />
<h3>Permits</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Permits"
:categories="chartData.permits"
>
</TodoChart>
<button class="sidebar-button" @click="navigateTo('/tasks?subject=Permit')">
View Permits
</button>
</div>
</template>
</Card>
<!-- Permits Finalization Widget -->
<Card >
<template #header>
<div class="widget-header">
<DoubleCheck class="widget-icon" />
<h3>Permit Finalizations</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Permit Finalization"
:categories="chartData.permitFinalizations"
>
</TodoChart>
<button class="sidebar-button" @click="navigateTo('/jobs')">
View Finalizations
</button>
</div>
</template>
</Card>
<!-- Warranties Widget -->
<Card >
<template #header>
<div class="widget-header">
<Shield class="widget-icon" />
<h3>Warranties</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Warranty Claims"
:categories="chartData.warranties"
>
</TodoChart>
<button class="sidebar-button" @click="navigateTo('/jobs')">
View Warranties
</button>
</div>
</template>
</Card>
<!-- Incomplete Bids Widget -->
<Card >
<template #header>
<div class="widget-header">
<Edit class="widget-icon" />
<h3>Incomplete Bids</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Incomplete Bids"
:categories="chartData.bids"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/calendar')">
Incomplete Bids
</button>
</div>
</template>
</Card>
<!-- Unapproved Estimates Widget -->
<Card >
<template #header>
<div class="widget-header">
<ChatBubbleQuestion class="widget-icon" />
<h3>Unapproved Estimates</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Unapproved Estimates"
:categories="chartData.estimates"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/estimates')">
Unapproved Estimates
</button>
</div>
</template>
</Card>
<!-- Half Down Widget -->
<Card >
<template #header>
<div class="widget-header">
<CreditCard class="widget-icon" />
<h3>Half Down Payments</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Half Down Payments"
:categories="chartData.halfDown"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/jobs')">
Half Down Payments
</button>
</div>
</template>
</Card>
<!-- 15 Day Follow Up Widget -->
<Card >
<template #header>
<div class="widget-header">
<Calendar class="widget-icon" />
<h3>15 Day Follow Ups</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="15 Day Follow Ups"
:categories="chartData.fifteenDayFollowups"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/calendar')">
View Follow Ups
</button>
</div>
</template>
</Card>
<!-- Late Balances Widget -->
<Card >
<template #header>
<div class="widget-header">
<CardNoAccess class="widget-icon" />
<h3>Late Balances</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Late Balances"
:categories="chartData.lateBalances"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/invoices')">
Late Balances
</button>
</div>
</template>
</Card>
<!-- Backflow Tests Widget -->
<Card >
<template #header>
<div class="widget-header">
<WateringSoil class="widget-icon" />
<h3>Backflow Tests</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Backflow Tests"
:categories="chartData.backflows"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/jobs')">
Late Balances
</button>
</div>
</template>
</Card>
<!-- Curbing Widget -->
<Card >
<template #header>
<div class="widget-header">
<SoilAlt class="widget-icon" />
<h3>Curbing</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Curbing"
:categories="chartData.curbing"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/tasks?subject=Curbing')">
Curbing
</button>
</div>
</template>
</Card>
<!-- Hyrdoseeding Widget -->
<Card >
<template #header>
<div class="widget-header">
<SoilAlt class="widget-icon" />
<h3>Hydroseeding</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Hydroseeding"
:categories="chartData.hydroseed"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/tasks?subject=Hydroseed')">
Hydroseeding
</button>
</div>
</template>
</Card>
<!-- Machines Widget -->
<Card >
<template #header>
<div class="widget-header">
<Truck class="widget-icon" />
<h3>Machines</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Machines"
:categories="chartData.machines"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/jobs')">
Machines
</button>
</div>
</template>
</Card>
<!-- Deliveries Widget -->
<Card >
<template #header>
<div class="widget-header">
<Truck class="widget-icon" />
<h3>Deliveries</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<TodoChart
title="Deliveries"
:categories="chartData.deliveries"
>
</TodoChart>
<button class="sidebar-button"
@click="navigateTo('/jobs')">
Deliveries
</button>
</div>
</template>
</Card>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
//import Card from "primevue/card";
import Card from "../common/Card.vue";
import Tag from "primevue/tag";
import { Calendar, Community, Hammer, PathArrowSolid, Clock, Shield, ShieldSearch,
ClipboardCheck, DoubleCheck, CreditCard, CardNoAccess, ChatBubbleQuestion, Edit,
WateringSoil, Soil, Truck, SoilAlt } from "@iconoir/vue";
import Api from "../../api.js";
import DataUtils from "../../utils.js";
import { useNotificationStore } from "../../stores/notifications-primevue";
import { useCompanyStore } from "../../stores/company.js";
//import SimpleChart from "../common/SimpleChart.vue";
import TodoChart from "../common/TodoChart.vue";
const router = useRouter();
const defaultColors = ['blue', 'green', 'red'];
// Dummy data from utils
const clientData = ref(DataUtils.dummyClientData);
const jobData = ref(DataUtils.dummyJobData);
const locatesTodoNumber = ref(0);
const locatesCompletedNumber = ref(0);
const permitsTodoNumber = ref(0);
const permitsCompletedNumber = ref(0);
const permitFinalizationsTodoNumber = ref(35);
const permitFinalizationsCompletedNumber = ref(2);
const warrantyTodoNumber = ref(0);
const warrantyCompletedNumber = ref(10);
const chartData = ref({
locates: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
permits: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
curbing: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
hydroseed: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
permitFinalizations: {labels: ["Todo", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
warranties: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
fifteenDayFollowups: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
lateBalances: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
backflows: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
machines: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
deliveries: {labels: ["To-do", "Completed", "Overdue"], data: [0, 0, 0], colors: defaultColors},
bids: {labels: ["Unscheduled"], data: [0], colors: ['red']},
estimates: {labels: ["Draft", "Submitted"], data: [0, 0], colors: ['orange', 'blue']},
halfDown: {labels: ["Unpaid"], data: [0], colors: ['red']},
});
const notifications = useNotificationStore();
const companyStore = useCompanyStore();
// 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);
};
const loadChartData = async() => {
chartData.value.locates.data = await Api.getTasksDue("Locate", companyStore.currentCompany);
chartData.value.permits.data = await Api.getTasksDue("Permit(s)", companyStore.currentCompany);
chartData.value.curbing.data = await Api.getTasksDue("Curbing", companyStore.currentCompany);
chartData.value.hydroseed.data = await Api.getTasksDue("Hydroseed", companyStore.currentCompany);
chartData.value.permitFinalizations.data = await Api.getTasksDue("Permit Close-out", companyStore.currentCompany);
chartData.value.warranties.data = await Api.getTasksDue("Warranty", companyStore.currentCompany);
chartData.value.fifteenDayFollowups.data = await Api.getTasksDue("15-Day QA", companyStore.currentCompany);
chartData.value.backflows.data = await Api.getTasksDue("Backflow", companyStore.currentCompany);
//Uncomment below when we can check if half-down payments have/can been paid
//chartData.value.estimates.data = await Api.getEstimatesHalfDownCount();
chartData.value.bids.data = await Api.getIncompleteBidsCount(companyStore.currentCompany);
chartData.value.estimates.data = await Api.getUnapprovedEstimatesCount(companyStore.currentCompany);
};
onMounted(async() => {
notifications.addWarning("Dashboard metrics are based on dummy data for demonstration purposes. UPDATES COMING SOON!");
await loadChartData();
});
watch(() => companyStore.currentCompany, async (newCompany, oldCompany) => {
await loadChartData();
});
</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: flex;
flex-wrap: wrap;
gap: 20px;
}
.widget-header {
display: flex;
align-items: center;
gap: 10px;
padding: 20px 20px 0;
}
.widget-icon {
color: var(--theme-primary-strong);
width: 24px;
height: 24px;
}
.widget-header h3 {
margin: 0;
color: #2c3e50;
font-size: 1.2rem;
}
.widget-content {
display: flex;
flex-direction: column;
margin: 0;
width: 200px;
align-items: center;
padding: 20px 20px 20px;
/*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;
}
.sidebar-button {
justify-content: center;
}
/* 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>