add timesheets page and update homepage modules:
This commit is contained in:
parent
0c921a3897
commit
b70e08026d
@ -45,6 +45,12 @@ class Api {
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getTimesheetData() {
|
||||
const data = DataUtils.dummyTimesheetData;
|
||||
console.log("DEBUG: API - getTimesheetData result: ", data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static async getDocsList(doctype, fields = []) {
|
||||
const docs = await frappe.db.get_list(doctype, { fields });
|
||||
console.log(`DEBUG: API - Fetched ${doctype} list: `, docs);
|
||||
|
||||
@ -8,18 +8,18 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<Calendar class="widget-icon" />
|
||||
<h3>Upcoming Schedule</h3>
|
||||
<h3>Service Calendar</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="widget-content">
|
||||
<div class="metric">
|
||||
<span class="metric-number">8</span>
|
||||
<span class="metric-label">Appointments Today</span>
|
||||
<span class="metric-label">Services Scheduled Today</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-number">15</span>
|
||||
<span class="metric-label">This Week</span>
|
||||
<span class="metric-label">Services This Week</span>
|
||||
</div>
|
||||
<Button
|
||||
label="View Calendar"
|
||||
@ -36,21 +36,21 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<Community class="widget-icon" />
|
||||
<h3>Client Overview</h3>
|
||||
<h3>Client Contact List</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" />
|
||||
<Tag severity="success" value="5 Active Jobs" />
|
||||
<Tag severity="warning" value="3 Pending Estimates" />
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-number">{{ clientData.length }}</span>
|
||||
<span class="metric-label">Total Clients</span>
|
||||
<span class="metric-label">Total Client Records</span>
|
||||
</div>
|
||||
<Button
|
||||
label="Manage Clients"
|
||||
label="View Client List"
|
||||
size="small"
|
||||
outlined
|
||||
@click="navigateTo('/clients')"
|
||||
@ -64,7 +64,7 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<Hammer class="widget-icon" />
|
||||
<h3>Active Jobs</h3>
|
||||
<h3>Job Management</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
@ -77,10 +77,10 @@
|
||||
<span class="metric-number">{{
|
||||
jobData.filter((job) => job.overAllStatus === "in progress").length
|
||||
}}</span>
|
||||
<span class="metric-label">Active Jobs</span>
|
||||
<span class="metric-label">Jobs In Progress</span>
|
||||
</div>
|
||||
<Button
|
||||
label="View Jobs"
|
||||
label="View All Jobs"
|
||||
size="small"
|
||||
outlined
|
||||
@click="navigateTo('/jobs')"
|
||||
@ -94,21 +94,21 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<PathArrowSolid class="widget-icon" />
|
||||
<h3>Route Planning</h3>
|
||||
<h3>Service Routes</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="widget-content">
|
||||
<div class="metric">
|
||||
<span class="metric-number">6</span>
|
||||
<span class="metric-label">Routes Today</span>
|
||||
<span class="metric-label">Active Routes Today</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-number">45.2</span>
|
||||
<span class="metric-label">Avg. Miles/Route</span>
|
||||
<span class="metric-label">Avg. Miles per Route</span>
|
||||
</div>
|
||||
<Button
|
||||
label="Plan Routes"
|
||||
label="View Routes"
|
||||
size="small"
|
||||
outlined
|
||||
@click="navigateTo('/routes')"
|
||||
@ -122,18 +122,18 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<Clock class="widget-icon" />
|
||||
<h3>Time Tracking</h3>
|
||||
<h3>Employee Timesheets</h3>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="widget-content">
|
||||
<div class="metric">
|
||||
<span class="metric-number">32.5</span>
|
||||
<span class="metric-label">Hours This Week</span>
|
||||
<span class="metric-label">Total Hours This Week</span>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<Tag severity="success" value="5 Completed" />
|
||||
<Tag severity="warning" value="2 Pending" />
|
||||
<Tag severity="success" value="5 Approved" />
|
||||
<Tag severity="warning" value="2 Pending Approval" />
|
||||
</div>
|
||||
<Button
|
||||
label="View Timesheets"
|
||||
@ -150,20 +150,21 @@
|
||||
<template #header>
|
||||
<div class="widget-header">
|
||||
<HistoricShield class="widget-icon" />
|
||||
<h3>Warranties</h3>
|
||||
<h3>Warranty Claims</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>
|
||||
<span class="metric-number">10</span>
|
||||
<span class="metric-label">Open Claims</span>
|
||||
</div>
|
||||
<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>
|
||||
<Button
|
||||
label="Manage Warranties"
|
||||
label="View Claims"
|
||||
size="small"
|
||||
outlined
|
||||
@click="navigateTo('/warranties')"
|
||||
|
||||
@ -1,9 +1,849 @@
|
||||
<template lang="">
|
||||
<div>
|
||||
<h2>TimeSheets Page</h2>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
<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",
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user