add timesheets page and update homepage modules:
This commit is contained in:
parent
0c921a3897
commit
b70e08026d
@ -45,6 +45,12 @@ class Api {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getTimesheetData() {
|
||||||
|
const data = DataUtils.dummyTimesheetData;
|
||||||
|
console.log("DEBUG: API - getTimesheetData result: ", data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
static async getDocsList(doctype, fields = []) {
|
static async getDocsList(doctype, fields = []) {
|
||||||
const docs = await frappe.db.get_list(doctype, { fields });
|
const docs = await frappe.db.get_list(doctype, { fields });
|
||||||
console.log(`DEBUG: API - Fetched ${doctype} list: `, docs);
|
console.log(`DEBUG: API - Fetched ${doctype} list: `, docs);
|
||||||
|
|||||||
@ -8,18 +8,18 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<Calendar class="widget-icon" />
|
<Calendar class="widget-icon" />
|
||||||
<h3>Upcoming Schedule</h3>
|
<h3>Service Calendar</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="widget-content">
|
<div class="widget-content">
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">8</span>
|
<span class="metric-number">8</span>
|
||||||
<span class="metric-label">Appointments Today</span>
|
<span class="metric-label">Services Scheduled Today</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">15</span>
|
<span class="metric-number">15</span>
|
||||||
<span class="metric-label">This Week</span>
|
<span class="metric-label">Services This Week</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="View Calendar"
|
label="View Calendar"
|
||||||
@ -36,21 +36,21 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<Community class="widget-icon" />
|
<Community class="widget-icon" />
|
||||||
<h3>Client Overview</h3>
|
<h3>Client Contact List</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="widget-content">
|
<div class="widget-content">
|
||||||
<div class="status-row">
|
<div class="status-row">
|
||||||
<Tag severity="success" value="5 Active" />
|
<Tag severity="success" value="5 Active Jobs" />
|
||||||
<Tag severity="warning" value="3 Pending" />
|
<Tag severity="warning" value="3 Pending Estimates" />
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">{{ clientData.length }}</span>
|
<span class="metric-number">{{ clientData.length }}</span>
|
||||||
<span class="metric-label">Total Clients</span>
|
<span class="metric-label">Total Client Records</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="Manage Clients"
|
label="View Client List"
|
||||||
size="small"
|
size="small"
|
||||||
outlined
|
outlined
|
||||||
@click="navigateTo('/clients')"
|
@click="navigateTo('/clients')"
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<Hammer class="widget-icon" />
|
<Hammer class="widget-icon" />
|
||||||
<h3>Active Jobs</h3>
|
<h3>Job Management</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
@ -77,10 +77,10 @@
|
|||||||
<span class="metric-number">{{
|
<span class="metric-number">{{
|
||||||
jobData.filter((job) => job.overAllStatus === "in progress").length
|
jobData.filter((job) => job.overAllStatus === "in progress").length
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="metric-label">Active Jobs</span>
|
<span class="metric-label">Jobs In Progress</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="View Jobs"
|
label="View All Jobs"
|
||||||
size="small"
|
size="small"
|
||||||
outlined
|
outlined
|
||||||
@click="navigateTo('/jobs')"
|
@click="navigateTo('/jobs')"
|
||||||
@ -94,21 +94,21 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<PathArrowSolid class="widget-icon" />
|
<PathArrowSolid class="widget-icon" />
|
||||||
<h3>Route Planning</h3>
|
<h3>Service Routes</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="widget-content">
|
<div class="widget-content">
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">6</span>
|
<span class="metric-number">6</span>
|
||||||
<span class="metric-label">Routes Today</span>
|
<span class="metric-label">Active Routes Today</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">45.2</span>
|
<span class="metric-number">45.2</span>
|
||||||
<span class="metric-label">Avg. Miles/Route</span>
|
<span class="metric-label">Avg. Miles per Route</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="Plan Routes"
|
label="View Routes"
|
||||||
size="small"
|
size="small"
|
||||||
outlined
|
outlined
|
||||||
@click="navigateTo('/routes')"
|
@click="navigateTo('/routes')"
|
||||||
@ -122,18 +122,18 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<Clock class="widget-icon" />
|
<Clock class="widget-icon" />
|
||||||
<h3>Time Tracking</h3>
|
<h3>Employee Timesheets</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="widget-content">
|
<div class="widget-content">
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">32.5</span>
|
<span class="metric-number">32.5</span>
|
||||||
<span class="metric-label">Hours This Week</span>
|
<span class="metric-label">Total Hours This Week</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-row">
|
<div class="status-row">
|
||||||
<Tag severity="success" value="5 Completed" />
|
<Tag severity="success" value="5 Approved" />
|
||||||
<Tag severity="warning" value="2 Pending" />
|
<Tag severity="warning" value="2 Pending Approval" />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="View Timesheets"
|
label="View Timesheets"
|
||||||
@ -150,20 +150,21 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<HistoricShield class="widget-icon" />
|
<HistoricShield class="widget-icon" />
|
||||||
<h3>Warranties</h3>
|
<h3>Warranty Claims</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="widget-content">
|
<div class="widget-content">
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric-number">23</span>
|
<span class="metric-number">10</span>
|
||||||
<span class="metric-label">Active Warranties</span>
|
<span class="metric-label">Open Claims</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-row">
|
<div class="status-row">
|
||||||
<Tag severity="danger" value="3 Expiring Soon" />
|
<Tag severity="success" value="3 Completed" />
|
||||||
|
<Tag severity="warning" value="4 In Progress" />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label="Manage Warranties"
|
label="View Claims"
|
||||||
size="small"
|
size="small"
|
||||||
outlined
|
outlined
|
||||||
@click="navigateTo('/warranties')"
|
@click="navigateTo('/warranties')"
|
||||||
|
|||||||
@ -1,9 +1,849 @@
|
|||||||
<template lang="">
|
<template>
|
||||||
|
<div class="timesheets-page">
|
||||||
|
<div class="timesheets-header">
|
||||||
|
<h2>Employee Timesheets</h2>
|
||||||
|
<p class="timesheets-subtitle">
|
||||||
|
Track time, manage approvals, and monitor labor costs
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls Section -->
|
||||||
|
<div class="controls-section">
|
||||||
|
<div class="filter-controls">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Week Filter:</label>
|
||||||
|
<v-select
|
||||||
|
v-model="selectedWeek"
|
||||||
|
:items="weekOptions"
|
||||||
|
item-title="label"
|
||||||
|
item-value="value"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
@update:modelValue="filterByWeek"
|
||||||
|
></v-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Employee:</label>
|
||||||
|
<v-select
|
||||||
|
v-model="selectedEmployee"
|
||||||
|
:items="employeeOptions"
|
||||||
|
item-title="label"
|
||||||
|
item-value="value"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
@update:modelValue="filterByEmployee"
|
||||||
|
></v-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Status:</label>
|
||||||
|
<v-select
|
||||||
|
v-model="selectedStatus"
|
||||||
|
:items="statusOptions"
|
||||||
|
item-title="label"
|
||||||
|
item-value="value"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
clearable
|
||||||
|
@update:modelValue="filterByStatus"
|
||||||
|
></v-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<v-btn color="primary" @click="addNewTimesheet" prepend-icon="mdi-plus">
|
||||||
|
Add Timesheet
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="success"
|
||||||
|
@click="bulkApprove"
|
||||||
|
prepend-icon="mdi-check-all"
|
||||||
|
:disabled="!hasSelectedForApproval"
|
||||||
|
>
|
||||||
|
Bulk Approve
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="info" @click="exportTimesheets" prepend-icon="mdi-download">
|
||||||
|
Export
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary Cards -->
|
||||||
|
<div class="summary-cards">
|
||||||
|
<v-card class="summary-card">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="summary-content">
|
||||||
|
<v-icon color="primary" size="large">mdi-clock</v-icon>
|
||||||
|
<div class="summary-text">
|
||||||
|
<div class="summary-number">{{ totalHoursThisWeek }}</div>
|
||||||
|
<div class="summary-label">Hours This Week</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card class="summary-card">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="summary-content">
|
||||||
|
<v-icon color="warning" size="large">mdi-clock-alert</v-icon>
|
||||||
|
<div class="summary-text">
|
||||||
|
<div class="summary-number">{{ pendingApprovals }}</div>
|
||||||
|
<div class="summary-label">Pending Approval</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card class="summary-card">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="summary-content">
|
||||||
|
<v-icon color="success" size="large">mdi-currency-usd</v-icon>
|
||||||
|
<div class="summary-text">
|
||||||
|
<div class="summary-number">${{ totalLaborCost }}</div>
|
||||||
|
<div class="summary-label">Labor Cost</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<v-card class="summary-card">
|
||||||
|
<v-card-text>
|
||||||
|
<div class="summary-content">
|
||||||
|
<v-icon color="info" size="large">mdi-account-group</v-icon>
|
||||||
|
<div class="summary-text">
|
||||||
|
<div class="summary-number">{{ activeEmployees }}</div>
|
||||||
|
<div class="summary-label">Active Employees</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Timesheet Table -->
|
||||||
|
<div class="timesheets-table-container">
|
||||||
|
<DataTable
|
||||||
|
:data="filteredTableData"
|
||||||
|
:columns="columns"
|
||||||
|
:filters="filters"
|
||||||
|
@row-click="viewTimesheetDetails"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Timesheet Details Modal -->
|
||||||
|
<v-dialog v-model="timesheetDialog" max-width="1000px" persistent>
|
||||||
|
<v-card v-if="selectedTimesheet">
|
||||||
|
<v-card-title class="d-flex justify-space-between align-center pa-4">
|
||||||
<div>
|
<div>
|
||||||
<h2>TimeSheets Page</h2>
|
<h3>Timesheet Details - {{ selectedTimesheet.timesheetId }}</h3>
|
||||||
|
<span class="text-subtitle-1 text-medium-emphasis">
|
||||||
|
{{ selectedTimesheet.employee }} -
|
||||||
|
{{ formatDate(selectedTimesheet.date) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center gap-2">
|
||||||
|
<v-chip :color="getStatusColor(selectedTimesheet.status)" size="small">
|
||||||
|
{{ selectedTimesheet.status.toUpperCase() }}
|
||||||
|
</v-chip>
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
variant="text"
|
||||||
|
@click="timesheetDialog = false"
|
||||||
|
></v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-text class="pa-0">
|
||||||
|
<div class="timesheet-details-container">
|
||||||
|
<!-- Left Panel - Basic Info -->
|
||||||
|
<div class="details-left-panel pa-4">
|
||||||
|
<h4 class="mb-3">Time & Job Information</h4>
|
||||||
|
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-account</v-icon>
|
||||||
|
<span class="label">Employee:</span>
|
||||||
|
<span class="value">{{ selectedTimesheet.employee }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-calendar</v-icon>
|
||||||
|
<span class="label">Date:</span>
|
||||||
|
<span class="value">{{
|
||||||
|
formatDate(selectedTimesheet.date)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-briefcase</v-icon>
|
||||||
|
<span class="label">Job ID:</span>
|
||||||
|
<span class="value">{{ selectedTimesheet.jobId }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-account-hard-hat</v-icon>
|
||||||
|
<span class="label">Customer:</span>
|
||||||
|
<span class="value">{{ selectedTimesheet.customer }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-map-marker</v-icon>
|
||||||
|
<span class="label">Address:</span>
|
||||||
|
<span class="value">{{ selectedTimesheet.address }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<v-icon class="mr-2" size="small">mdi-wrench</v-icon>
|
||||||
|
<span class="label">Task:</span>
|
||||||
|
<span class="value">{{
|
||||||
|
selectedTimesheet.taskDescription
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time Details -->
|
||||||
|
<h4 class="mt-4 mb-3">Time Details</h4>
|
||||||
|
<div class="time-details">
|
||||||
|
<div class="time-row">
|
||||||
|
<span class="time-label">Clock In:</span>
|
||||||
|
<span class="time-value">{{ selectedTimesheet.clockIn }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-row">
|
||||||
|
<span class="time-label">Clock Out:</span>
|
||||||
|
<span class="time-value">{{
|
||||||
|
selectedTimesheet.clockOut
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-row">
|
||||||
|
<span class="time-label">Break Time:</span>
|
||||||
|
<span class="time-value"
|
||||||
|
>{{ selectedTimesheet.breakTime }} min</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="time-row">
|
||||||
|
<span class="time-label">Regular Hours:</span>
|
||||||
|
<span class="time-value">{{
|
||||||
|
selectedTimesheet.regularHours
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-row">
|
||||||
|
<span class="time-label">Overtime Hours:</span>
|
||||||
|
<span class="time-value">{{
|
||||||
|
selectedTimesheet.overtimeHours
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="time-row total-row">
|
||||||
|
<span class="time-label">Total Hours:</span>
|
||||||
|
<span class="time-value">{{
|
||||||
|
selectedTimesheet.totalHours
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pay Details -->
|
||||||
|
<h4 class="mt-4 mb-3">Pay Calculation</h4>
|
||||||
|
<div class="pay-details">
|
||||||
|
<div class="pay-row">
|
||||||
|
<span class="pay-label">Regular Rate:</span>
|
||||||
|
<span class="pay-value"
|
||||||
|
>${{ selectedTimesheet.hourlyRate }}/hr</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="pay-row">
|
||||||
|
<span class="pay-label">Overtime Rate:</span>
|
||||||
|
<span class="pay-value"
|
||||||
|
>${{ selectedTimesheet.overtimeRate }}/hr</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="pay-row">
|
||||||
|
<span class="pay-label">Mileage:</span>
|
||||||
|
<span class="pay-value"
|
||||||
|
>{{ selectedTimesheet.mileage }} miles</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="pay-row total-row">
|
||||||
|
<span class="pay-label">Total Pay:</span>
|
||||||
|
<span class="pay-value"
|
||||||
|
>${{ selectedTimesheet.totalPay }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Panel - Equipment, Materials, Notes -->
|
||||||
|
<div class="details-right-panel pa-4">
|
||||||
|
<h4 class="mb-3">Work Details</h4>
|
||||||
|
|
||||||
|
<!-- Equipment Used -->
|
||||||
|
<div class="work-section mb-4">
|
||||||
|
<h5 class="mb-2">Equipment Used:</h5>
|
||||||
|
<v-chip-group>
|
||||||
|
<v-chip
|
||||||
|
v-for="equipment in selectedTimesheet.equipment"
|
||||||
|
:key="equipment"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{{ equipment }}
|
||||||
|
</v-chip>
|
||||||
|
</v-chip-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Materials Used -->
|
||||||
|
<div class="work-section mb-4">
|
||||||
|
<h5 class="mb-2">Materials Used:</h5>
|
||||||
|
<div class="materials-list">
|
||||||
|
<div
|
||||||
|
v-for="material in selectedTimesheet.materials"
|
||||||
|
:key="material"
|
||||||
|
class="material-item"
|
||||||
|
>
|
||||||
|
<v-icon size="small" class="mr-2"
|
||||||
|
>mdi-package-variant</v-icon
|
||||||
|
>
|
||||||
|
{{ material }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Work Notes -->
|
||||||
|
<div class="work-section mb-4">
|
||||||
|
<h5 class="mb-2">Work Notes:</h5>
|
||||||
|
<div class="notes-content">
|
||||||
|
{{ selectedTimesheet.notes }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Approval Status -->
|
||||||
|
<div class="approval-section">
|
||||||
|
<h5 class="mb-2">Approval Status:</h5>
|
||||||
|
<div class="approval-info">
|
||||||
|
<div v-if="selectedTimesheet.approved" class="approved-info">
|
||||||
|
<v-icon color="success" class="mr-2"
|
||||||
|
>mdi-check-circle</v-icon
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="approval-text">
|
||||||
|
Approved by {{ selectedTimesheet.approvedBy }}
|
||||||
|
</div>
|
||||||
|
<div class="approval-date">
|
||||||
|
{{ formatDate(selectedTimesheet.approvedDate) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="pending-info">
|
||||||
|
<v-icon color="warning" class="mr-2">mdi-clock</v-icon>
|
||||||
|
<span>Pending approval</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions class="pa-4">
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey" variant="outlined" @click="timesheetDialog = false">
|
||||||
|
Close
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-if="!selectedTimesheet.approved"
|
||||||
|
color="success"
|
||||||
|
@click="approveTimesheet(selectedTimesheet)"
|
||||||
|
>
|
||||||
|
Approve
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="primary" @click="editTimesheet(selectedTimesheet)"> Edit </v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {};
|
<script setup>
|
||||||
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
import DataTable from "../DataTable.vue";
|
||||||
|
import Api from "../../api";
|
||||||
|
import { FilterMatchMode } from "@primevue/core";
|
||||||
|
|
||||||
|
// Reactive data
|
||||||
|
const tableData = ref([]);
|
||||||
|
const filteredTableData = ref([]);
|
||||||
|
const timesheetDialog = ref(false);
|
||||||
|
const selectedTimesheet = ref(null);
|
||||||
|
|
||||||
|
// Filter controls
|
||||||
|
const selectedWeek = ref("current");
|
||||||
|
const selectedEmployee = ref(null);
|
||||||
|
const selectedStatus = ref(null);
|
||||||
|
|
||||||
|
// Filter options
|
||||||
|
const weekOptions = [
|
||||||
|
{ label: "Current Week", value: "current" },
|
||||||
|
{ label: "Last Week", value: "last" },
|
||||||
|
{ label: "Last 2 Weeks", value: "last2" },
|
||||||
|
{ label: "All Weeks", value: "all" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const employeeOptions = ref([]);
|
||||||
|
const statusOptions = [
|
||||||
|
{ label: "Draft", value: "draft" },
|
||||||
|
{ label: "Submitted", value: "submitted" },
|
||||||
|
{ label: "Approved", value: "approved" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Table configuration
|
||||||
|
const columns = [
|
||||||
|
{ label: "Timesheet ID", fieldName: "timesheetId", type: "text", sortable: true },
|
||||||
|
{ label: "Employee", fieldName: "employee", type: "text", sortable: true, filterable: true },
|
||||||
|
{ label: "Date", fieldName: "date", type: "text", sortable: true },
|
||||||
|
{ label: "Job ID", fieldName: "jobId", type: "text", sortable: true },
|
||||||
|
{ label: "Customer", fieldName: "customer", type: "text", sortable: true },
|
||||||
|
{ label: "Total Hours", fieldName: "totalHours", type: "text", sortable: true },
|
||||||
|
{ label: "Status", fieldName: "status", type: "status", sortable: true },
|
||||||
|
{ label: "Total Pay", fieldName: "totalPayFormatted", type: "text", sortable: true },
|
||||||
|
{ label: "Actions", fieldName: "actions", type: "button", sortable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
employee: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const totalHoursThisWeek = computed(() => {
|
||||||
|
const currentWeekData = getCurrentWeekTimesheets();
|
||||||
|
return currentWeekData
|
||||||
|
.reduce((total, timesheet) => total + timesheet.totalHours, 0)
|
||||||
|
.toFixed(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const pendingApprovals = computed(() => {
|
||||||
|
return tableData.value.filter((ts) => !ts.approved).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalLaborCost = computed(() => {
|
||||||
|
const currentWeekData = getCurrentWeekTimesheets();
|
||||||
|
const total = currentWeekData.reduce((sum, timesheet) => sum + timesheet.totalPay, 0);
|
||||||
|
return total.toLocaleString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeEmployees = computed(() => {
|
||||||
|
const uniqueEmployees = new Set(tableData.value.map((ts) => ts.employee));
|
||||||
|
return uniqueEmployees.size;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasSelectedForApproval = computed(() => {
|
||||||
|
// In a real implementation, this would check selected rows
|
||||||
|
return pendingApprovals.value > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const getCurrentWeekTimesheets = () => {
|
||||||
|
const currentWeekStart = getCurrentWeekStart();
|
||||||
|
return tableData.value.filter((timesheet) => {
|
||||||
|
const timesheetDate = new Date(timesheet.date);
|
||||||
|
return timesheetDate >= currentWeekStart;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCurrentWeekStart = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const dayOfWeek = now.getDay();
|
||||||
|
const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // Adjust for Sunday
|
||||||
|
return new Date(now.setDate(diff));
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeekStart = (weeksAgo) => {
|
||||||
|
const now = new Date();
|
||||||
|
const dayOfWeek = now.getDay();
|
||||||
|
const diff = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1) - weeksAgo * 7;
|
||||||
|
return new Date(now.setDate(diff));
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByWeek = () => {
|
||||||
|
applyFilters();
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByEmployee = () => {
|
||||||
|
applyFilters();
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterByStatus = () => {
|
||||||
|
applyFilters();
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyFilters = () => {
|
||||||
|
let filtered = [...tableData.value];
|
||||||
|
|
||||||
|
// Week filter
|
||||||
|
if (selectedWeek.value !== "all") {
|
||||||
|
const weeksAgo =
|
||||||
|
selectedWeek.value === "current" ? 0 : selectedWeek.value === "last" ? 1 : 2;
|
||||||
|
const weekStart = getWeekStart(weeksAgo);
|
||||||
|
const weekEnd =
|
||||||
|
selectedWeek.value === "last2"
|
||||||
|
? new Date()
|
||||||
|
: new Date(weekStart.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
filtered = filtered.filter((timesheet) => {
|
||||||
|
const timesheetDate = new Date(timesheet.date);
|
||||||
|
return timesheetDate >= weekStart && timesheetDate < weekEnd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Employee filter
|
||||||
|
if (selectedEmployee.value) {
|
||||||
|
filtered = filtered.filter((ts) => ts.employee === selectedEmployee.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status filter
|
||||||
|
if (selectedStatus.value) {
|
||||||
|
filtered = filtered.filter((ts) => ts.status === selectedStatus.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredTableData.value = filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewTimesheetDetails = (event) => {
|
||||||
|
const timesheetId = event.data.timesheetId;
|
||||||
|
const timesheet = tableData.value.find((ts) => ts.timesheetId === timesheetId);
|
||||||
|
if (timesheet) {
|
||||||
|
selectedTimesheet.value = timesheet;
|
||||||
|
timesheetDialog.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNewTimesheet = () => {
|
||||||
|
console.log("Add new timesheet clicked");
|
||||||
|
// TODO: Implement add timesheet functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkApprove = () => {
|
||||||
|
console.log("Bulk approve clicked");
|
||||||
|
// TODO: Implement bulk approval functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportTimesheets = () => {
|
||||||
|
console.log("Export timesheets clicked");
|
||||||
|
// TODO: Implement export functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
const approveTimesheet = (timesheet) => {
|
||||||
|
timesheet.approved = true;
|
||||||
|
timesheet.approvedBy = "Current User"; // In real app, get from user context
|
||||||
|
timesheet.approvedDate = new Date().toISOString().split("T")[0];
|
||||||
|
timesheet.status = "approved";
|
||||||
|
console.log("Approved timesheet:", timesheet.timesheetId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const editTimesheet = (timesheet) => {
|
||||||
|
console.log("Edit timesheet:", timesheet.timesheetId);
|
||||||
|
// TODO: Implement edit functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
switch (status?.toLowerCase()) {
|
||||||
|
case "approved":
|
||||||
|
return "success";
|
||||||
|
case "submitted":
|
||||||
|
return "warning";
|
||||||
|
case "draft":
|
||||||
|
return "grey";
|
||||||
|
default:
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return "N/A";
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load data on component mount
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const data = await Api.getTimesheetData();
|
||||||
|
|
||||||
|
// Transform data for table display
|
||||||
|
tableData.value = data.map((timesheet) => ({
|
||||||
|
...timesheet,
|
||||||
|
totalPayFormatted: `$${timesheet.totalPay.toLocaleString()}`,
|
||||||
|
actions: "View Details",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Set up employee options
|
||||||
|
const uniqueEmployees = [...new Set(data.map((ts) => ts.employee))];
|
||||||
|
employeeOptions.value = uniqueEmployees.map((emp) => ({ label: emp, value: emp }));
|
||||||
|
|
||||||
|
// Apply initial filters
|
||||||
|
applyFilters();
|
||||||
|
|
||||||
|
console.log("Loaded timesheets:", tableData.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading timesheets:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang=""></style>
|
|
||||||
|
<style scoped>
|
||||||
|
.timesheets-page {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheets-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheets-header h2 {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheets-subtitle {
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-number {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheets-table-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheet-details-container {
|
||||||
|
display: flex;
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-left-panel {
|
||||||
|
flex: 1;
|
||||||
|
border-right: 1px solid #e0e0e0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-right-panel {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .label {
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 80px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-details,
|
||||||
|
.pay-details {
|
||||||
|
background: white;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-row,
|
||||||
|
.pay-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-row:last-child,
|
||||||
|
.pay-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-label,
|
||||||
|
.pay-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-value,
|
||||||
|
.pay-value {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-section h5 {
|
||||||
|
color: #1976d2;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.materials-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #1976d2;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-content {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 4px solid #1976d2;
|
||||||
|
font-style: italic;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approval-section {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approved-info,
|
||||||
|
.pending-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approval-text {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approval-date {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.controls-section {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-cards {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timesheet-details-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-left-panel {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1281,6 +1281,364 @@ class DataUtils {
|
|||||||
systemType: "Commercial Irrigation",
|
systemType: "Commercial Irrigation",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static dummyTimesheetData = [
|
||||||
|
// Current week entries (Oct 25-31, 2025)
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
timesheetId: "TS-2025-001",
|
||||||
|
employee: "Mike Thompson",
|
||||||
|
employeeId: "EMP001",
|
||||||
|
date: "2025-10-25",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB001",
|
||||||
|
customer: "John Doe",
|
||||||
|
address: "123 Lane Dr, Cityville, MN",
|
||||||
|
taskDescription: "Sprinkler System Installation - Zone 1-4",
|
||||||
|
clockIn: "07:30",
|
||||||
|
clockOut: "17:00",
|
||||||
|
breakTime: 30, // minutes
|
||||||
|
regularHours: 9.0,
|
||||||
|
overtimeHours: 0.5,
|
||||||
|
totalHours: 9.5,
|
||||||
|
status: "submitted",
|
||||||
|
approved: false,
|
||||||
|
approvedBy: null,
|
||||||
|
approvedDate: null,
|
||||||
|
hourlyRate: 28.5,
|
||||||
|
overtimeRate: 42.75,
|
||||||
|
totalPay: 292.88,
|
||||||
|
notes: "Completed main line installation and zone 1-4 heads",
|
||||||
|
equipment: ["Trencher", "Compactor", "Hand tools"],
|
||||||
|
mileage: 45,
|
||||||
|
materials: ["PVC Pipe - 200ft", "Sprinkler Heads - 15", "Valves - 4"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
timesheetId: "TS-2025-002",
|
||||||
|
employee: "Sarah Johnson",
|
||||||
|
employeeId: "EMP002",
|
||||||
|
date: "2025-10-25",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB007",
|
||||||
|
customer: "Chris Taylor",
|
||||||
|
address: "159 Spruce Ct, Capital City, WA",
|
||||||
|
taskDescription: "Zone Expansion Install",
|
||||||
|
clockIn: "08:00",
|
||||||
|
clockOut: "16:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 8.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Lisa Anderson",
|
||||||
|
approvedDate: "2025-10-26",
|
||||||
|
hourlyRate: 26.0,
|
||||||
|
overtimeRate: 39.0,
|
||||||
|
totalPay: 208.0,
|
||||||
|
notes: "Added 3 new zones, tested all systems",
|
||||||
|
equipment: ["Boring machine", "Hand tools"],
|
||||||
|
mileage: 32,
|
||||||
|
materials: [
|
||||||
|
"Irrigation tubing - 150ft",
|
||||||
|
"Sprinkler Heads - 8",
|
||||||
|
"Control wire - 300ft",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
timesheetId: "TS-2025-003",
|
||||||
|
employee: "David Martinez",
|
||||||
|
employeeId: "EMP003",
|
||||||
|
date: "2025-10-25",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB012",
|
||||||
|
customer: "Sophia White",
|
||||||
|
address: "258 Palm Blvd, Riverside, AZ",
|
||||||
|
taskDescription: "Drip System Repair & Maintenance",
|
||||||
|
clockIn: "07:00",
|
||||||
|
clockOut: "15:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 8.0,
|
||||||
|
status: "draft",
|
||||||
|
approved: false,
|
||||||
|
approvedBy: null,
|
||||||
|
approvedDate: null,
|
||||||
|
hourlyRate: 24.5,
|
||||||
|
overtimeRate: 36.75,
|
||||||
|
totalPay: 196.0,
|
||||||
|
notes: "Repaired multiple leaks in drip lines, replaced emitters",
|
||||||
|
equipment: ["Hand tools", "Pressure gauge"],
|
||||||
|
mileage: 28,
|
||||||
|
materials: ["Drip tubing - 50ft", "Emitters - 25", "Fittings - misc"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
timesheetId: "TS-2025-004",
|
||||||
|
employee: "Chris Wilson",
|
||||||
|
employeeId: "EMP004",
|
||||||
|
date: "2025-10-24",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB003",
|
||||||
|
customer: "Mike Johnson",
|
||||||
|
address: "789 Pine Rd, Villagetown, TX",
|
||||||
|
taskDescription: "Controller Programming & System Testing",
|
||||||
|
clockIn: "09:00",
|
||||||
|
clockOut: "17:30",
|
||||||
|
breakTime: 60,
|
||||||
|
regularHours: 7.5,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 7.5,
|
||||||
|
status: "submitted",
|
||||||
|
approved: false,
|
||||||
|
approvedBy: null,
|
||||||
|
approvedDate: null,
|
||||||
|
hourlyRate: 30.0,
|
||||||
|
overtimeRate: 45.0,
|
||||||
|
totalPay: 225.0,
|
||||||
|
notes: "Programmed new smart controller, tested all 8 zones",
|
||||||
|
equipment: ["Laptop", "Multimeter", "Hand tools"],
|
||||||
|
mileage: 42,
|
||||||
|
materials: ["Smart Controller - 1", "Wire nuts - 10"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
timesheetId: "TS-2025-005",
|
||||||
|
employee: "Lisa Anderson",
|
||||||
|
employeeId: "EMP005",
|
||||||
|
date: "2025-10-24",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB017",
|
||||||
|
customer: "Ethan Walker",
|
||||||
|
address: "456 Walnut Rd, Meadowbrook, ND",
|
||||||
|
taskDescription: "Backflow Prevention Installation",
|
||||||
|
clockIn: "08:30",
|
||||||
|
clockOut: "16:00",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 7.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 7.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Mike Thompson",
|
||||||
|
approvedDate: "2025-10-25",
|
||||||
|
hourlyRate: 27.5,
|
||||||
|
overtimeRate: 41.25,
|
||||||
|
totalPay: 192.5,
|
||||||
|
notes: "Installed backflow preventer, pressure testing completed",
|
||||||
|
equipment: ["Pipe cutter", "Threading machine", "Pressure tester"],
|
||||||
|
mileage: 38,
|
||||||
|
materials: ["Backflow Preventer - 1", "Copper fittings - misc", "Solder"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
timesheetId: "TS-2025-006",
|
||||||
|
employee: "Mike Thompson",
|
||||||
|
employeeId: "EMP001",
|
||||||
|
date: "2025-10-23",
|
||||||
|
weekOf: "2025-10-21",
|
||||||
|
jobId: "JOB015",
|
||||||
|
customer: "Andrew Lewis",
|
||||||
|
address: "357 Dogwood Dr, Lakeview, VT",
|
||||||
|
taskDescription: "System Winterization",
|
||||||
|
clockIn: "07:30",
|
||||||
|
clockOut: "15:00",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 7.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 7.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Lisa Anderson",
|
||||||
|
approvedDate: "2025-10-24",
|
||||||
|
hourlyRate: 28.5,
|
||||||
|
overtimeRate: 42.75,
|
||||||
|
totalPay: 199.5,
|
||||||
|
notes: "Winterized entire system, drained all lines and components",
|
||||||
|
equipment: ["Air compressor", "Hand tools"],
|
||||||
|
mileage: 55,
|
||||||
|
materials: ["Compressed air", "Antifreeze - 2 gallons"],
|
||||||
|
},
|
||||||
|
// Previous week entries (Oct 14-20, 2025)
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
timesheetId: "TS-2025-007",
|
||||||
|
employee: "Sarah Johnson",
|
||||||
|
employeeId: "EMP002",
|
||||||
|
date: "2025-10-20",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "JOB010",
|
||||||
|
customer: "Olivia Thomas",
|
||||||
|
address: "147 Cypress St, Uptown, GA",
|
||||||
|
taskDescription: "Emergency Repair - Main Line Leak",
|
||||||
|
clockIn: "07:00",
|
||||||
|
clockOut: "18:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.0,
|
||||||
|
overtimeHours: 3.0,
|
||||||
|
totalHours: 11.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Mike Thompson",
|
||||||
|
approvedDate: "2025-10-21",
|
||||||
|
hourlyRate: 26.0,
|
||||||
|
overtimeRate: 39.0,
|
||||||
|
totalPay: 325.0,
|
||||||
|
notes: "Emergency call - repaired major main line break",
|
||||||
|
equipment: ["Excavator", "Pipe cutter", "Hand tools"],
|
||||||
|
mileage: 67,
|
||||||
|
materials: ["PVC Pipe - 20ft", "Couplings - 4", "Pipe cement"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
timesheetId: "TS-2025-008",
|
||||||
|
employee: "David Martinez",
|
||||||
|
employeeId: "EMP003",
|
||||||
|
date: "2025-10-19",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "JOB005",
|
||||||
|
customer: "David Wilson",
|
||||||
|
address: "654 Cedar Blvd, Borough, NY",
|
||||||
|
taskDescription: "Final System Testing & Cleanup",
|
||||||
|
clockIn: "08:00",
|
||||||
|
clockOut: "16:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 8.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Lisa Anderson",
|
||||||
|
approvedDate: "2025-10-20",
|
||||||
|
hourlyRate: 24.5,
|
||||||
|
overtimeRate: 36.75,
|
||||||
|
totalPay: 196.0,
|
||||||
|
notes: "Final system testing, site cleanup, customer walkthrough",
|
||||||
|
equipment: ["Hand tools", "Cleanup supplies"],
|
||||||
|
mileage: 25,
|
||||||
|
materials: ["Grass seed - 5 lbs", "Topsoil - 2 bags"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
timesheetId: "TS-2025-009",
|
||||||
|
employee: "Chris Wilson",
|
||||||
|
employeeId: "EMP004",
|
||||||
|
date: "2025-10-18",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "JOB009",
|
||||||
|
customer: "Joshua Anderson",
|
||||||
|
address: "852 Aspen Rd, Bigcity, NJ",
|
||||||
|
taskDescription: "Valve Replacement & Zone Testing",
|
||||||
|
clockIn: "08:30",
|
||||||
|
clockOut: "17:00",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 8.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Mike Thompson",
|
||||||
|
approvedDate: "2025-10-19",
|
||||||
|
hourlyRate: 30.0,
|
||||||
|
overtimeRate: 45.0,
|
||||||
|
totalPay: 240.0,
|
||||||
|
notes: "Replaced faulty zone 3 valve, tested all zones for proper operation",
|
||||||
|
equipment: ["Trenching shovel", "Hand tools", "Multimeter"],
|
||||||
|
mileage: 48,
|
||||||
|
materials: ["Zone Valve - 1", "Wire nuts - 5", "Electrical tape"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
timesheetId: "TS-2025-010",
|
||||||
|
employee: "Lisa Anderson",
|
||||||
|
employeeId: "EMP005",
|
||||||
|
date: "2025-10-17",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "JOB014",
|
||||||
|
customer: "Isabella Clark",
|
||||||
|
address: "753 Magnolia Ct, Hilltown, SC",
|
||||||
|
taskDescription: "Sprinkler Head Adjustments & Coverage Testing",
|
||||||
|
clockIn: "09:00",
|
||||||
|
clockOut: "16:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 7.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 7.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Sarah Johnson",
|
||||||
|
approvedDate: "2025-10-18",
|
||||||
|
hourlyRate: 27.5,
|
||||||
|
overtimeRate: 41.25,
|
||||||
|
totalPay: 192.5,
|
||||||
|
notes: "Adjusted all sprinkler heads for optimal coverage, marked areas needing attention",
|
||||||
|
equipment: ["Hand tools", "Measuring tape", "Spray paint"],
|
||||||
|
mileage: 33,
|
||||||
|
materials: ["Sprinkler Head Adjustment Tools", "Marking flags - 20"],
|
||||||
|
},
|
||||||
|
// Older entries for reporting
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
timesheetId: "TS-2025-011",
|
||||||
|
employee: "Mike Thompson",
|
||||||
|
employeeId: "EMP001",
|
||||||
|
date: "2025-10-16",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "JOB006",
|
||||||
|
customer: "Sarah Brown",
|
||||||
|
address: "987 Birch Ln, Metropolis, IL",
|
||||||
|
taskDescription: "Site Survey & Design Planning",
|
||||||
|
clockIn: "08:00",
|
||||||
|
clockOut: "15:30",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 7.0,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 7.0,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Lisa Anderson",
|
||||||
|
approvedDate: "2025-10-17",
|
||||||
|
hourlyRate: 28.5,
|
||||||
|
overtimeRate: 42.75,
|
||||||
|
totalPay: 199.5,
|
||||||
|
notes: "Completed site survey, created design plan for 4-zone expansion",
|
||||||
|
equipment: ["Measuring wheel", "Stake flags", "Camera"],
|
||||||
|
mileage: 40,
|
||||||
|
materials: ["Survey stakes - 50", "Flagging tape - 1 roll"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
timesheetId: "TS-2025-012",
|
||||||
|
employee: "Sarah Johnson",
|
||||||
|
employeeId: "EMP002",
|
||||||
|
date: "2025-10-15",
|
||||||
|
weekOf: "2025-10-14",
|
||||||
|
jobId: "MAINT-001",
|
||||||
|
customer: "Multiple Properties",
|
||||||
|
address: "Various - See route sheet",
|
||||||
|
taskDescription: "Routine Maintenance Route - North Valley",
|
||||||
|
clockIn: "07:00",
|
||||||
|
clockOut: "16:00",
|
||||||
|
breakTime: 30,
|
||||||
|
regularHours: 8.5,
|
||||||
|
overtimeHours: 0,
|
||||||
|
totalHours: 8.5,
|
||||||
|
status: "approved",
|
||||||
|
approved: true,
|
||||||
|
approvedBy: "Mike Thompson",
|
||||||
|
approvedDate: "2025-10-16",
|
||||||
|
hourlyRate: 26.0,
|
||||||
|
overtimeRate: 39.0,
|
||||||
|
totalPay: 221.0,
|
||||||
|
notes: "Completed maintenance on 8 properties - routine checks and minor adjustments",
|
||||||
|
equipment: ["Service truck", "Hand tools", "Pressure gauge"],
|
||||||
|
mileage: 85,
|
||||||
|
materials: ["Sprinkler heads - 3", "Nozzles - 5", "Wire nuts - 10"],
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DataUtils;
|
export default DataUtils;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user