Added service plan. Password change

This commit is contained in:
2025-09-06 12:28:44 -04:00
parent 9d86b4a60e
commit 3282229116
14 changed files with 977 additions and 51 deletions

View File

@@ -0,0 +1,230 @@
<!-- src/pages/service/ServicePlans.vue -->
<template>
<div class="flex">
<div class="w-full px-4 md:px-10 py-4">
<!-- Breadcrumbs & Title -->
<div class="text-sm breadcrumbs">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li><router-link :to="{ name: 'ServiceHome' }">Service</router-link></li>
<li>Service Plans</li>
</ul>
</div>
<h1 class="text-3xl font-bold mt-4">Service Plans</h1>
<!-- Main Content Card -->
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Active Service Contracts</h2>
<div v-if="!isLoading" class="badge badge-ghost">{{ servicePlans.length }} contracts found</div>
</div>
<div class="divider"></div>
<!-- Loading State -->
<div v-if="isLoading" class="text-center p-10">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-2">Loading service plans...</p>
</div>
<!-- Empty State -->
<div v-else-if="servicePlans.length === 0" class="text-center p-10">
<p>No active service contracts found.</p>
</div>
<!-- Data Display -->
<div v-else>
<!-- DESKTOP VIEW: Table -->
<div class="overflow-x-auto hidden xl:block">
<table class="table w-full">
<thead>
<tr>
<th>Customer</th>
<th>Plan Type</th>
<th>Contract Years</th>
<th>Start Date</th>
<th>End Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr v-for="plan in servicePlans" :key="plan.id" class="hover:bg-blue-600">
<td class="align-top">
<div class="font-semibold">{{ plan.customer_name }}</div>
<div class="text-sm opacity-70">{{ plan.customer_address }}, {{ plan.customer_town }}</div>
</td>
<td class="align-top">
<span class="badge badge-sm text-white"
:style="{ 'background-color': getPlanColor(plan.contract_plan) }">
{{ getPlanName(plan.contract_plan) }}
</span>
</td>
<td class="align-top">{{ plan.contract_years }} year{{ plan.contract_years > 1 ? 's' : '' }}</td>
<td class="align-top">{{ formatDate(plan.contract_start_date) }}</td>
<td class="align-top">{{ formatEndDate(plan.contract_start_date, plan.contract_years) }}</td>
<td class="align-top">
<span class="badge" :class="getStatusBadge(plan.contract_start_date, plan.contract_years)">
{{ getStatusText(plan.contract_start_date, plan.contract_years) }}
</span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- MOBILE VIEW: Cards -->
<div class="xl:hidden space-y-4">
<div v-for="plan in servicePlans" :key="plan.id" class="card bg-base-100 shadow-md">
<div class="card-body p-4">
<div class="flex justify-between items-start">
<div>
<h2 class="card-title text-base">{{ plan.customer_name }}</h2>
<p class="text-xs text-gray-400">{{ plan.customer_address }}, {{ plan.customer_town }}</p>
</div>
<div class="badge badge-outline" :style="{ 'border-color': getPlanColor(plan.contract_plan), color: getPlanColor(plan.contract_plan) }">
{{ getPlanName(plan.contract_plan) }}
</div>
</div>
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
<p><strong class="font-semibold">Years:</strong> {{ plan.contract_years }}</p>
<p><strong class="font-semibold">Start:</strong> {{ formatDate(plan.contract_start_date) }}</p>
<p><strong class="font-semibold">End:</strong> {{ formatEndDate(plan.contract_start_date, plan.contract_years) }}</p>
<p><strong class="font-semibold">Status:</strong>
<span class="badge badge-sm ml-1" :class="getStatusBadge(plan.contract_start_date, plan.contract_years)">
{{ getStatusText(plan.contract_start_date, plan.contract_years) }}
</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
import dayjs from 'dayjs';
interface ServicePlan {
id: number;
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
contract_plan: number;
contract_years: number;
contract_start_date: string;
}
export default defineComponent({
name: 'ServicePlans',
components: { Footer },
data() {
return {
user: null,
servicePlans: [] as ServicePlan[],
isLoading: true,
}
},
created() {
this.userStatus();
this.fetchServicePlans();
},
methods: {
async fetchServicePlans(): Promise<void> {
this.isLoading = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
this.servicePlans = response.data;
} catch (error) {
console.error("Failed to fetch service plans:", error);
} finally {
this.isLoading = false;
}
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
this.user = response.data.user;
}
})
.catch(() => {
this.user = null
})
},
getPlanName(planType: number): string {
const planNames: { [key: number]: string } = {
1: 'Standard',
2: 'Premium'
};
return planNames[planType] || 'Unknown';
},
getPlanColor(planType: number): string {
const planColors: { [key: number]: string } = {
1: 'blue',
2: 'gold'
};
return planColors[planType] || 'gray';
},
formatDate(dateString: string): string {
if (!dateString) return 'N/A';
return dayjs(dateString).format('MMM D, YYYY');
},
formatEndDate(startDate: string, years: number): string {
if (!startDate) return 'N/A';
return dayjs(startDate).add(years, 'year').format('MMM D, YYYY');
},
getStatusText(startDate: string, years: number): string {
if (!startDate) return 'Unknown';
const endDate = dayjs(startDate).add(years, 'year');
const now = dayjs();
if (now.isAfter(endDate)) {
return 'Expired';
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
return 'Expiring Soon';
} else {
return 'Active';
}
},
getStatusBadge(startDate: string, years: number): string {
if (!startDate) return 'badge-ghost';
const endDate = dayjs(startDate).add(years, 'year');
const now = dayjs();
if (now.isAfter(endDate)) {
return 'badge-error';
} else if (now.isAfter(endDate.subtract(30, 'day'))) {
return 'badge-warning';
} else {
return 'badge-success';
}
}
},
})
</script>

View File

@@ -4,6 +4,7 @@ import ServicePast from './ServicePast.vue'
import CalendarCustomer from './calender/CalendarCustomer.vue'
import ServiceCalendar from './ServiceCalendar.vue'
import ServiceToday from './ServiceToday.vue'
import ServicePlans from './ServicePlans.vue'
const serviceRoutes = [
{
@@ -34,6 +35,11 @@ const serviceRoutes = [
name: 'ServiceToday',
component: ServiceToday,
},
{
path: '/service/plans',
name: 'ServicePlans',
component: ServicePlans,
},
]
export default serviceRoutes