Added service plan. Password change
This commit is contained in:
230
src/pages/service/ServicePlans.vue
Normal file
230
src/pages/service/ServicePlans.vue
Normal 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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user