build out mock views
This commit is contained in:
parent
6dac3bfb02
commit
403b29a8b8
@ -31,7 +31,7 @@ import SideBar from "./components/SideBar.vue";
|
|||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
min-width: 800px;
|
min-width: 800px;
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
height: 80vh;
|
min-height: 84vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#display-content {
|
#display-content {
|
||||||
|
|||||||
@ -14,6 +14,16 @@ class Api {
|
|||||||
// }
|
// }
|
||||||
// console.log("DEBUG: API - Fetched Client Details: ", data);
|
// console.log("DEBUG: API - Fetched Client Details: ", data);
|
||||||
const data = DataUtils.dummyClientData;
|
const data = DataUtils.dummyClientData;
|
||||||
|
console.log("DEBUG: API - getClientDetails result: ", data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getJobDetails() {
|
||||||
|
const data = DataUtils.dummyJobData.map((job) => ({
|
||||||
|
...job,
|
||||||
|
stepProgress: DataUtils.calculateStepProgress(job.steps),
|
||||||
|
}));
|
||||||
|
console.log("DEBUG: API - getJobDetails result: ", data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,41 +1,52 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div id="paging-controls">
|
<DataTable
|
||||||
<p>Show rows:</p>
|
:value="data"
|
||||||
<button class="page-num-button">25</button>
|
:rowsPerPageOptions="[5, 10, 20, 50]"
|
||||||
<button class="page-num-button">50</button>
|
:paginator="true"
|
||||||
<button class="page-num-button">100</button>
|
:rows="10"
|
||||||
<button class="page-turn-button">Prev</button>
|
sortMode="multiple"
|
||||||
<button class="page-turn-button">Next</button>
|
removableSort
|
||||||
</div>
|
filterDisplay="row"
|
||||||
<div>
|
v-model:filters="filterRef"
|
||||||
<DataTable :value="data">
|
scrollable
|
||||||
<Column
|
scrollHeight="70vh"
|
||||||
v-for="col in columns"
|
v-model:selection="selectedRows"
|
||||||
:key="col.fieldName"
|
selectionMode="multiple"
|
||||||
:field="col.fieldName"
|
metaKeySelection="true"
|
||||||
:header="col.label"
|
dataKey="id"
|
||||||
>
|
>
|
||||||
<template #body="slotProps">
|
<Column
|
||||||
<template v-if="col.type === 'status'">
|
v-for="col in columns"
|
||||||
<Badge
|
:key="col.fieldName"
|
||||||
:value="slotProps.data[col.fieldName]"
|
:field="col.fieldName"
|
||||||
:severity="getBadgeColor(slotProps.data[col.fieldName])"
|
:header="col.label"
|
||||||
:size="large"
|
:sortable="col.sortable"
|
||||||
/>
|
>
|
||||||
</template>
|
<template v-if="col.filterable === true" #filter="{ filterModel, filterCallback }">
|
||||||
<template v-else>
|
<InputText
|
||||||
{{ slotProps.data[col.fieldName] }}
|
v-model="filterModel.value"
|
||||||
</template>
|
type="text"
|
||||||
</template>
|
@input="filterCallback()"
|
||||||
</Column>
|
placeholder="Search by name"
|
||||||
</DataTable>
|
/>
|
||||||
</div>
|
</template>
|
||||||
|
<template v-if="col.type === 'status'" #body="slotProps">
|
||||||
|
<Tag
|
||||||
|
:value="slotProps.data[col.fieldName]"
|
||||||
|
:severity="getBadgeColor(slotProps.data[col.fieldName])"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps } from "vue";
|
import { defineProps } from "vue";
|
||||||
import DataTable from "primevue/datatable";
|
import DataTable from "primevue/datatable";
|
||||||
import Column from "primevue/column";
|
import Column from "primevue/column";
|
||||||
import Badge from "primevue/badge";
|
import Tag from "primevue/tag";
|
||||||
|
import InputText from "primevue/inputtext";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { FilterMatchMode } from "@primevue/core";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: {
|
columns: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -45,15 +56,24 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
filters: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
|
}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filterRef = ref(props.filters);
|
||||||
|
const selectedRows = ref();
|
||||||
|
|
||||||
const getBadgeColor = (status) => {
|
const getBadgeColor = (status) => {
|
||||||
console.log("DEBUG: - getBadgeColor status", status);
|
console.log("DEBUG: - getBadgeColor status", status);
|
||||||
switch (status?.toLowerCase()) {
|
switch (status?.toLowerCase()) {
|
||||||
case "completed":
|
case "completed":
|
||||||
return "success"; // green
|
return "success"; // green
|
||||||
case "in progress":
|
case "in progress":
|
||||||
case "warn":
|
return "warn";
|
||||||
return "warning"; // yellow
|
|
||||||
case "not started":
|
case "not started":
|
||||||
return "danger"; // red
|
return "danger"; // red
|
||||||
default:
|
default:
|
||||||
|
|||||||
0
frontend/src/components/Modal.vue
Normal file
0
frontend/src/components/Modal.vue
Normal file
@ -11,6 +11,7 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
HistoricShield,
|
HistoricShield,
|
||||||
} from "@iconoir/vue";
|
} from "@iconoir/vue";
|
||||||
|
import SpeedDial from "primevue/speeddial";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const categories = [
|
const categories = [
|
||||||
@ -19,10 +20,40 @@ const categories = [
|
|||||||
{ name: "Clients", icon: Community, url: "/clients" },
|
{ name: "Clients", icon: Community, url: "/clients" },
|
||||||
{ name: "Jobs", icon: Hammer, url: "/jobs" },
|
{ name: "Jobs", icon: Hammer, url: "/jobs" },
|
||||||
{ name: "Routes", icon: PathArrowSolid, url: "/routes" },
|
{ name: "Routes", icon: PathArrowSolid, url: "/routes" },
|
||||||
{ name: "Create", icon: MultiplePagesPlus, url: "/create" },
|
|
||||||
{ name: "Time Sheets", icon: Clock, url: "/timesheets" },
|
{ name: "Time Sheets", icon: Clock, url: "/timesheets" },
|
||||||
{ name: "Warranties", icon: HistoricShield, url: "/warranties" },
|
{ name: "Warranties", icon: HistoricShield, url: "/warranties" },
|
||||||
|
{
|
||||||
|
name: "Create New",
|
||||||
|
icon: MultiplePagesPlus,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const items = ref([
|
||||||
|
{
|
||||||
|
label: "Client",
|
||||||
|
command: () => {
|
||||||
|
frappe.new_doc("Customer");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Estimate",
|
||||||
|
command: () => {
|
||||||
|
frappe.new_doc("Estimate");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Job",
|
||||||
|
command: () => {
|
||||||
|
frappe.new_doc("Job");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Invoice",
|
||||||
|
command: () => {
|
||||||
|
frappe.new_doc("Sales Invoice");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
const handleCategoryClick = (category) => {
|
const handleCategoryClick = (category) => {
|
||||||
router.push(category.url);
|
router.push(category.url);
|
||||||
};
|
};
|
||||||
@ -30,19 +61,44 @@ const handleCategoryClick = (category) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="sidebar" class="sidebar">
|
<div id="sidebar" class="sidebar">
|
||||||
<button
|
<template v-for="category in categories">
|
||||||
v-for="category in categories"
|
<template v-if="category.url">
|
||||||
:class="[
|
<button
|
||||||
'sidebar-button',
|
:class="[
|
||||||
router.currentRoute.value.path === category.url ? 'active' : '',
|
'sidebar-button',
|
||||||
]"
|
router.currentRoute.value.path === category.url ? 'active' : '',
|
||||||
:key="category.name"
|
]"
|
||||||
@click="handleCategoryClick(category)"
|
:key="category.name"
|
||||||
>
|
@click="handleCategoryClick(category)"
|
||||||
<component :is="category.icon" class="button-icon" /><span class="button-text">{{
|
>
|
||||||
category.name
|
<component :is="category.icon" class="button-icon" /><span
|
||||||
}}</span>
|
class="button-text"
|
||||||
</button>
|
>{{ category.name }}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<SpeedDial :model="items" direction="down" type="linear" radius="50">
|
||||||
|
<template #button="{ toggleCallback }">
|
||||||
|
<button
|
||||||
|
class="sidebar-button"
|
||||||
|
@click="toggleCallback"
|
||||||
|
:key="category.name"
|
||||||
|
>
|
||||||
|
<component :is="category.icon" class="button-icon" /><span
|
||||||
|
class="button-text"
|
||||||
|
>{{ category.name }}</span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template #item="{ item, toggleCallback }">
|
||||||
|
<button class="create-item" @click="toggleCallback" :key="item.label">
|
||||||
|
<span class="p-menuitem-text">{{ item.label }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</SpeedDial>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -61,6 +117,15 @@ const handleCategoryClick = (category) => {
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-item {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.button-text {
|
.button-text {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
@ -85,5 +150,6 @@ const handleCategoryClick = (category) => {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -2,20 +2,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<H2>Client Contact List</H2>
|
<H2>Client Contact List</H2>
|
||||||
<div id="filter-container" class="filter-container">
|
<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">
|
<button @click="onClick" id="add-customer-button" class="interaction-button">
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<DataTable :data="tableData" :columns="columns" />
|
<DataTable :data="tableData" :columns="columns" :filters="filters" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import DataTable from "../DataTable.vue";
|
import DataTable from "../DataTable.vue";
|
||||||
import Api from "../../api";
|
import Api from "../../api";
|
||||||
|
import { FilterMatchMode } from "@primevue/core";
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
|
|
||||||
@ -23,12 +21,27 @@ const onClick = () => {
|
|||||||
frappe.new_doc("Customer");
|
frappe.new_doc("Customer");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
fullName: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ label: "Name", fieldName: "fullName", type: "text" },
|
{
|
||||||
{ label: "Appt. Scheduled", fieldName: "appointmentScheduled", type: "status" },
|
label: "Name",
|
||||||
{ label: "Estimate Sent", fieldName: "estimateSent", type: "status" },
|
fieldName: "fullName",
|
||||||
{ label: "Payment Received", fieldName: "paymentReceived", type: "status" },
|
type: "text",
|
||||||
{ label: "Job Status", fieldName: "jobStatus", type: "status" },
|
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 () => {
|
onMounted(async () => {
|
||||||
if (tableData.value.length > 0) {
|
if (tableData.value.length > 0) {
|
||||||
|
|||||||
@ -1,9 +1,363 @@
|
|||||||
<template lang="">
|
<template>
|
||||||
<div>
|
<div class="dashboard">
|
||||||
<h2>Hello!</h2>
|
<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>
|
</div>
|
||||||
</template>
|
</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>
|
</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>
|
||||||
|
|||||||
@ -1,9 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2>Jobs</h2>
|
<h2>Jobs</h2>
|
||||||
|
<DataTable :data="tableData" :columns="columns" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script setup>
|
||||||
export default {};
|
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>
|
</script>
|
||||||
<style lang=""></style>
|
<style lang=""></style>
|
||||||
|
|||||||
5
frontend/src/globalSettings.js
Normal file
5
frontend/src/globalSettings.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Aura from "@primeuix/themes/aura";
|
||||||
|
|
||||||
|
export const globalSettings = {
|
||||||
|
theme: Aura,
|
||||||
|
};
|
||||||
@ -3,5 +3,16 @@ import "./style.css";
|
|||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import PrimeVue from "primevue/config";
|
import PrimeVue from "primevue/config";
|
||||||
|
import { globalSettings } from "./globalSettings";
|
||||||
|
|
||||||
createApp(App).use(router).use(PrimeVue).mount("#custom-ui-app");
|
createApp(App)
|
||||||
|
.use(router)
|
||||||
|
.use(PrimeVue, {
|
||||||
|
theme: {
|
||||||
|
options: {
|
||||||
|
darkModeSelector: false,
|
||||||
|
},
|
||||||
|
preset: globalSettings.theme,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mount("#custom-ui-app");
|
||||||
|
|||||||
36
frontend/src/stores/modal.js
Normal file
36
frontend/src/stores/modal.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useModalsStore = defineStore("modals", {
|
||||||
|
state: () => ({
|
||||||
|
isCreateClientOpen: false,
|
||||||
|
isCreateInvoiceOpen: false,
|
||||||
|
isCreateJobOpen: false,
|
||||||
|
isCreateEstimateOpen: false,
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
openCreateClient() {
|
||||||
|
this.isCreateClientOpen = true;
|
||||||
|
},
|
||||||
|
closeCreateClient() {
|
||||||
|
this.isCreateClientOpen = false;
|
||||||
|
},
|
||||||
|
openCreateInvoice() {
|
||||||
|
this.isCreateInvoiceOpen = true;
|
||||||
|
},
|
||||||
|
closeCreateInvoice() {
|
||||||
|
this.isCreateInvoiceOpen = false;
|
||||||
|
},
|
||||||
|
openCreateJob() {
|
||||||
|
this.isCreateJobOpen = true;
|
||||||
|
},
|
||||||
|
closeCreateJob() {
|
||||||
|
this.isCreateJobOpen = false;
|
||||||
|
},
|
||||||
|
openCreateEstimate() {
|
||||||
|
this.isCreateEstimateOpen = true;
|
||||||
|
},
|
||||||
|
closeCreateEstimate() {
|
||||||
|
this.isCreateEstimateOpen = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -5,29 +5,39 @@ class DataUtils {
|
|||||||
// return clients.map((client) => [clientName, client.]
|
// return clients.map((client) => [clientName, client.]
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
static calculateStepProgress(steps) {
|
||||||
|
if (!steps || steps.length === 0) return "0/0";
|
||||||
|
const completedSteps = steps.filter((step) => step.status === "completed").length;
|
||||||
|
return `${completedSteps}/${steps.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
static dummyClientData = [
|
static dummyClientData = [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
fullName: "John Doe 123 Lane Dr Cityville, MN",
|
fullName: "John Doe 123 Lane Dr Cityville, MN",
|
||||||
appointmentScheduled: "completed",
|
appointmentScheduled: "completed",
|
||||||
estimateSent: "pending",
|
estimateSent: "in progress",
|
||||||
paymentReceived: "not started",
|
paymentReceived: "not started",
|
||||||
jobStatus: "not started",
|
jobStatus: "not started",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
fullName: "Jane Smith 456 Oak St Townsville, CA",
|
fullName: "Jane Smith 456 Oak St Townsville, CA",
|
||||||
appointmentScheduled: "pending",
|
appointmentScheduled: "in progress",
|
||||||
estimateSent: "not started",
|
estimateSent: "not started",
|
||||||
paymentReceived: "not started",
|
paymentReceived: "not started",
|
||||||
jobStatus: "not started",
|
jobStatus: "not started",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
fullName: "Mike Johnson 789 Pine Rd Villagetown, TX",
|
fullName: "Mike Johnson 789 Pine Rd Villagetown, TX",
|
||||||
appointmentScheduled: "completed",
|
appointmentScheduled: "completed",
|
||||||
estimateSent: "completed",
|
estimateSent: "completed",
|
||||||
paymentReceived: "pending",
|
paymentReceived: "in progress",
|
||||||
jobStatus: "in progress",
|
jobStatus: "in progress",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
fullName: "Emily Davis 321 Maple Ave Hamlet, FL",
|
fullName: "Emily Davis 321 Maple Ave Hamlet, FL",
|
||||||
appointmentScheduled: "not started",
|
appointmentScheduled: "not started",
|
||||||
estimateSent: "not started",
|
estimateSent: "not started",
|
||||||
@ -35,12 +45,463 @@ class DataUtils {
|
|||||||
jobStatus: "not started",
|
jobStatus: "not started",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 5,
|
||||||
fullName: "David Wilson 654 Cedar Blvd Borough, NY",
|
fullName: "David Wilson 654 Cedar Blvd Borough, NY",
|
||||||
appointmentScheduled: "completed",
|
appointmentScheduled: "completed",
|
||||||
estimateSent: "completed",
|
estimateSent: "completed",
|
||||||
paymentReceived: "completed",
|
paymentReceived: "completed",
|
||||||
jobStatus: "completed",
|
jobStatus: "completed",
|
||||||
},
|
},
|
||||||
|
// create 30 more dummy entries
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
fullName: "Sarah Brown 987 Birch Ln Metropolis, IL",
|
||||||
|
appointmentScheduled: "in progress",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
fullName: "Chris Taylor 159 Spruce Ct Capital City, WA",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "completed",
|
||||||
|
paymentReceived: "in progress",
|
||||||
|
jobStatus: "in progress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
fullName: "Amanda Martinez 753 Willow Dr Smalltown, OH",
|
||||||
|
appointmentScheduled: "not started",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
fullName: "Joshua Anderson 852 Aspen Rd Bigcity, NJ",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "in progress",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
fullName: "Olivia Thomas 147 Cypress St Uptown, GA",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "completed",
|
||||||
|
paymentReceived: "completed",
|
||||||
|
jobStatus: "completed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
fullName: "Daniel Jackson 369 Fir Ave Downtown, MI",
|
||||||
|
appointmentScheduled: "in progress",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
fullName: "Sophia White 258 Palm Blvd Riverside, AZ",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "completed",
|
||||||
|
paymentReceived: "in progress",
|
||||||
|
jobStatus: "in progress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
fullName: "Matthew Harris 951 Olive Ln Seaside, OR",
|
||||||
|
appointmentScheduled: "not started",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
fullName: "Isabella Clark 753 Magnolia Ct Hilltown, SC",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "in progress",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
fullName: "Andrew Lewis 357 Dogwood Dr Lakeview, VT",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "completed",
|
||||||
|
paymentReceived: "completed",
|
||||||
|
jobStatus: "completed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
fullName: "Mia Robinson 159 Cherry St Forestville, KY",
|
||||||
|
appointmentScheduled: "in progress",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
fullName: "Ethan Walker 456 Walnut Rd Meadowbrook, ND",
|
||||||
|
appointmentScheduled: "completed",
|
||||||
|
estimateSent: "completed",
|
||||||
|
paymentReceived: "in progress",
|
||||||
|
jobStatus: "in progress",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
fullName: "Ava Hall 123 Poplar Ave Greenfield, MS",
|
||||||
|
appointmentScheduled: "not started",
|
||||||
|
estimateSent: "not started",
|
||||||
|
paymentReceived: "not started",
|
||||||
|
jobStatus: "not started",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
static dummyJobData = [
|
||||||
|
{
|
||||||
|
jobId: "JOB001",
|
||||||
|
foreman: "Mike Thompson",
|
||||||
|
crew: ["Worker A", "Worker B"],
|
||||||
|
address: "123 Lane Dr, Cityville, MN",
|
||||||
|
customer: "John Doe",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "in progress" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB002",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "456 Oak St, Townsville, CA",
|
||||||
|
customer: "Jane Smith",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "not started" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB003",
|
||||||
|
foreman: "Sarah Johnson",
|
||||||
|
crew: ["Worker C", "Worker D", "Worker E"],
|
||||||
|
address: "789 Pine Rd, Villagetown, TX",
|
||||||
|
customer: "Mike Johnson",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "in progress" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB004",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "321 Maple Ave, Hamlet, FL",
|
||||||
|
customer: "Emily Davis",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "not started" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB005",
|
||||||
|
foreman: "David Martinez",
|
||||||
|
crew: ["Worker F", "Worker G"],
|
||||||
|
address: "654 Cedar Blvd, Borough, NY",
|
||||||
|
customer: "David Wilson",
|
||||||
|
overAllStatus: "completed",
|
||||||
|
completionDate: "2024-10-15",
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "completed" },
|
||||||
|
{ stepName: "Job Completed", status: "completed" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB006",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "987 Birch Ln, Metropolis, IL",
|
||||||
|
customer: "Sarah Brown",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "in progress" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB007",
|
||||||
|
foreman: "Chris Wilson",
|
||||||
|
crew: ["Worker H", "Worker I"],
|
||||||
|
address: "159 Spruce Ct, Capital City, WA",
|
||||||
|
customer: "Chris Taylor",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "in progress" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB008",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "753 Willow Dr, Smalltown, OH",
|
||||||
|
customer: "Amanda Martinez",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "not started" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB009",
|
||||||
|
foreman: "Lisa Anderson",
|
||||||
|
crew: ["Worker J"],
|
||||||
|
address: "852 Aspen Rd, Bigcity, NJ",
|
||||||
|
customer: "Joshua Anderson",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "in progress" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB010",
|
||||||
|
foreman: "Robert Thomas",
|
||||||
|
crew: ["Worker K", "Worker L", "Worker M"],
|
||||||
|
address: "147 Cypress St, Uptown, GA",
|
||||||
|
customer: "Olivia Thomas",
|
||||||
|
overAllStatus: "completed",
|
||||||
|
completionDate: "2024-10-20",
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "completed" },
|
||||||
|
{ stepName: "Job Completed", status: "completed" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB011",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "369 Fir Ave, Downtown, MI",
|
||||||
|
customer: "Daniel Jackson",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "in progress" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB012",
|
||||||
|
foreman: "Maria White",
|
||||||
|
crew: ["Worker N", "Worker O"],
|
||||||
|
address: "258 Palm Blvd, Riverside, AZ",
|
||||||
|
customer: "Sophia White",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "in progress" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB013",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "951 Olive Ln, Seaside, OR",
|
||||||
|
customer: "Matthew Harris",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "not started" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB014",
|
||||||
|
foreman: "James Clark",
|
||||||
|
crew: ["Worker P"],
|
||||||
|
address: "753 Magnolia Ct, Hilltown, SC",
|
||||||
|
customer: "Isabella Clark",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "in progress" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB015",
|
||||||
|
foreman: "Patricia Lewis",
|
||||||
|
crew: ["Worker Q", "Worker R", "Worker S"],
|
||||||
|
address: "357 Dogwood Dr, Lakeview, VT",
|
||||||
|
customer: "Andrew Lewis",
|
||||||
|
overAllStatus: "completed",
|
||||||
|
completionDate: "2024-10-18",
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "completed" },
|
||||||
|
{ stepName: "Job Completed", status: "completed" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB016",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "159 Cherry St, Forestville, KY",
|
||||||
|
customer: "Mia Robinson",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "in progress" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB017",
|
||||||
|
foreman: "Kevin Walker",
|
||||||
|
crew: ["Worker T", "Worker U"],
|
||||||
|
address: "456 Walnut Rd, Meadowbrook, ND",
|
||||||
|
customer: "Ethan Walker",
|
||||||
|
overAllStatus: "in progress",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "completed" },
|
||||||
|
{ stepName: "Materials Received", status: "completed" },
|
||||||
|
{ stepName: "Permits", status: "completed" },
|
||||||
|
{ stepName: "Locate Utilities", status: "completed" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "completed" },
|
||||||
|
{ stepName: "Crew Assigned", status: "completed" },
|
||||||
|
{ stepName: "Job Started", status: "in progress" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobId: "JOB018",
|
||||||
|
foreman: null,
|
||||||
|
crew: [],
|
||||||
|
address: "123 Poplar Ave, Greenfield, MS",
|
||||||
|
customer: "Ava Hall",
|
||||||
|
overAllStatus: "not started",
|
||||||
|
completionDate: null,
|
||||||
|
steps: [
|
||||||
|
{ stepName: "Materials Ordered", status: "not started" },
|
||||||
|
{ stepName: "Materials Received", status: "not started" },
|
||||||
|
{ stepName: "Permits", status: "not started" },
|
||||||
|
{ stepName: "Locate Utilities", status: "not started" },
|
||||||
|
{ stepName: "Foreman Assigned", status: "not started" },
|
||||||
|
{ stepName: "Crew Assigned", status: "not started" },
|
||||||
|
{ stepName: "Job Started", status: "not started" },
|
||||||
|
{ stepName: "Job Completed", status: "not started" },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user