Refactor frontend to Composition API and improve UI/UX
Major Changes: - Migrate components from Options API to Composition API with <script setup> - Add centralized service layer (serviceService, deliveryService, adminService) - Implement new reusable components (EnhancedButton, EnhancedModal, StatCard, etc.) - Add theme store for consistent theming across application - Improve ServiceCalendar with federal holidays and better styling - Refactor customer profile and tank estimation components - Update all delivery and payment pages to use centralized services - Add utility functions for formatting and validation - Update Dockerfiles for better environment configuration - Enhance Tailwind config with custom design tokens UI Improvements: - Modern, premium design with glassmorphism effects - Improved form layouts with FloatingInput components - Better loading states and empty states - Enhanced modals and tables with consistent styling - Responsive design improvements across all pages Technical Improvements: - Strict TypeScript types throughout - Better error handling and validation - Removed deprecated api.js in favor of TypeScript services - Improved code organization and maintainability
This commit is contained in:
@@ -102,8 +102,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../services/auth.header';
|
||||
import serviceService from '../../services/serviceService';
|
||||
import customerService from '../../services/customerService';
|
||||
|
||||
// --- Interfaces ---
|
||||
interface ServiceCall { id: number; scheduled_date: string; customer_id: number; customer_name: string; customer_address: string; customer_town: string; type_service_call: number; description: string; service_cost: string }
|
||||
@@ -132,32 +132,33 @@ const serviceOptions = ref([
|
||||
{ text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
|
||||
])
|
||||
|
||||
// Watchers
|
||||
watch(() => props.service, (newVal) => {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
getCustomer(newVal.customer_id);
|
||||
getServiceParts(newVal.customer_id);
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// Functions
|
||||
// Functions (defined before watchers to avoid hoisting issues)
|
||||
const getCustomer = (customerId: number) => {
|
||||
customer.value = null;
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { customer.value = response.data; })
|
||||
customerService.getById(customerId)
|
||||
.then((response: any) => {
|
||||
if (response.data.customer) {
|
||||
customer.value = response.data.customer;
|
||||
} else if (response.data.ok && response.data.id) {
|
||||
customer.value = response.data as unknown as Customer;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
|
||||
}
|
||||
|
||||
const getServiceParts = (customerId: number) => {
|
||||
isLoadingParts.value = true;
|
||||
serviceParts.value = null;
|
||||
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { serviceParts.value = response.data; })
|
||||
serviceService.getPartsForCustomer(customerId)
|
||||
.then((response: any) => {
|
||||
if (response.data.parts) {
|
||||
if (Array.isArray(response.data.parts) && response.data.parts.length > 0) {
|
||||
serviceParts.value = response.data.parts[0];
|
||||
} else {
|
||||
serviceParts.value = response.data.parts as unknown as ServiceParts;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error: any) => { console.error("Failed to fetch service parts:", error); })
|
||||
.finally(() => { isLoadingParts.value = false; });
|
||||
}
|
||||
@@ -168,9 +169,8 @@ const saveChanges = async () => {
|
||||
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
|
||||
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
|
||||
try {
|
||||
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
|
||||
await serviceService.update(finalPayload.id!, finalPayload);
|
||||
emit('save-changes', finalPayload as ServiceCall);
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
@@ -201,4 +201,15 @@ const getStateAbbrev = (stateId: number | undefined | null): string => {
|
||||
const stateMap: { [key: number]: string } = { 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY' };
|
||||
return stateMap[stateId] || 'Unknown';
|
||||
}
|
||||
|
||||
// Watchers (after function definitions)
|
||||
watch(() => props.service, (newVal) => {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
getCustomer(newVal.customer_id);
|
||||
getServiceParts(newVal.customer_id);
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user