build out mock views

This commit is contained in:
Casey Wittrock 2025-10-24 03:40:53 -05:00
parent 6dac3bfb02
commit 403b29a8b8
12 changed files with 1066 additions and 70 deletions

View file

@ -2,20 +2,18 @@
<div>
<H2>Client Contact List</H2>
<div id="filter-container" class="filter-container">
<input placeholder="Type to Search" />
<p>Type:</p>
<select id="type-selector"></select>
<button @click="onClick" id="add-customer-button" class="interaction-button">
Add
</button>
</div>
<DataTable :data="tableData" :columns="columns" />
<DataTable :data="tableData" :columns="columns" :filters="filters" />
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import DataTable from "../DataTable.vue";
import Api from "../../api";
import { FilterMatchMode } from "@primevue/core";
const tableData = ref([]);
@ -23,12 +21,27 @@ const onClick = () => {
frappe.new_doc("Customer");
};
const filters = {
fullName: { value: null, matchMode: FilterMatchMode.CONTAINS },
};
const columns = [
{ label: "Name", fieldName: "fullName", type: "text" },
{ label: "Appt. Scheduled", fieldName: "appointmentScheduled", type: "status" },
{ label: "Estimate Sent", fieldName: "estimateSent", type: "status" },
{ label: "Payment Received", fieldName: "paymentReceived", type: "status" },
{ label: "Job Status", fieldName: "jobStatus", type: "status" },
{
label: "Name",
fieldName: "fullName",
type: "text",
sortable: true,
filterable: true,
},
{
label: "Appt. Scheduled",
fieldName: "appointmentScheduled",
type: "status",
sortable: true,
},
{ label: "Estimate Sent", fieldName: "estimateSent", type: "status", sortable: true },
{ label: "Payment Received", fieldName: "paymentReceived", type: "status", sortable: true },
{ label: "Job Status", fieldName: "jobStatus", type: "status", sortable: true },
];
onMounted(async () => {
if (tableData.value.length > 0) {

View file

@ -1,9 +1,363 @@
<template lang="">
<div>
<h2>Hello!</h2>
<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>Upcoming Schedule</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<div class="metric">
<span class="metric-number">8</span>
<span class="metric-label">Appointments Today</span>
</div>
<div class="metric">
<span class="metric-number">15</span>
<span class="metric-label">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 Overview</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<div class="status-row">
<Tag severity="success" value="5 Active" />
<Tag severity="warning" value="3 Pending" />
</div>
<div class="metric">
<span class="metric-number">{{ clientData.length }}</span>
<span class="metric-label">Total Clients</span>
</div>
<Button
label="Manage Clients"
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>Active Jobs</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">Active Jobs</span>
</div>
<Button
label="View 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>Route Planning</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<div class="metric">
<span class="metric-number">6</span>
<span class="metric-label">Routes Today</span>
</div>
<div class="metric">
<span class="metric-number">45.2</span>
<span class="metric-label">Avg. Miles/Route</span>
</div>
<Button
label="Plan 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>Time Tracking</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<div class="metric">
<span class="metric-number">32.5</span>
<span class="metric-label">Hours This Week</span>
</div>
<div class="status-row">
<Tag severity="success" value="5 Completed" />
<Tag severity="warning" value="2 Pending" />
</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>Warranties</h3>
</div>
</template>
<template #content>
<div class="widget-content">
<div class="metric">
<span class="metric-number">23</span>
<span class="metric-label">Active Warranties</span>
</div>
<div class="status-row">
<Tag severity="danger" value="3 Expiring Soon" />
</div>
<Button
label="Manage Warranties"
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>
export default {};
<script setup>
import { ref, computed } 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";
const router = useRouter();
// Dummy data from utils
const clientData = ref(DataUtils.dummyClientData);
const jobData = ref(DataUtils.dummyJobData);
// 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);
};
</script>
<style lang=""></style>
<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;
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>

View file

@ -1,9 +1,29 @@
<template>
<div>
<h2>Jobs</h2>
<DataTable :data="tableData" :columns="columns" />
</div>
</template>
<script>
export default {};
<script setup>
import DataTable from "../DataTable.vue";
import { ref, onMounted } from "vue";
import Api from "../../api";
const tableData = ref([]);
const columns = [
{ label: "Job ID", fieldName: "jobId", type: "text", sortable: true },
{ label: "Address", fieldName: "address", type: "text", sortable: true },
{ label: "Customer", fieldName: "customer", type: "text", sortable: true },
{ label: "Overall Status", fieldName: "overAllStatus", type: "status", sortable: true },
{ label: "Progress", fieldName: "stepProgress", type: "text", sortable: true },
];
onMounted(async () => {
if (tableData.value.length > 0) {
return;
}
let data = await Api.getJobDetails();
tableData.value = data;
});
</script>
<style lang=""></style>