2026-01-26 10:12:01 -05:00

358 lines
16 KiB
Svelte

<script lang="ts">
import {onMount} from 'svelte';
import {
serviceService,
accountService,
type Service,
type Account
} from '$lib/api.js';
import {profile, isAuthenticated} from '$lib/auth.js';
import {goto} from '$app/navigation';
import {page} from '$app/state';
import {format, parseISO} from 'date-fns';
// STATES
let service: Service | null = $state(null);
let account: Account | null = $state(null);
let loading = $state(true);
let serviceId = $state('');
let isAdmin = $state(false);
let isTeamLeader = $state(false);
// LOAD SERVICE DATA
onMount(async () => {
if (!$isAuthenticated) {
await goto('/login');
return;
}
isAdmin = $profile?.role === 'admin';
isTeamLeader = $profile?.role === 'team_leader';
serviceId = page.params.id;
if (!serviceId) {
await goto('/services');
return;
}
try {
loading = true;
// Fetch service details
service = await serviceService.getById(serviceId);
// Fetch related account
if (service) {
const accountId = typeof service.account === 'object'
? service.account.id
: service.account;
account = await accountService.getById(accountId);
}
loading = false;
} catch (error) {
console.error('Error loading service:', error);
loading = false;
}
});
// UPDATE SERVICE STATUS
async function updateStatus(newStatus: string) {
if (!service) return;
if (confirm(`Are you sure you want to mark this service as ${newStatus}?`)) {
try {
await serviceService.patch(service.id, {
status: newStatus as 'scheduled' | 'in_progress' | 'completed' | 'cancelled'
});
// Refresh service data
service = await serviceService.getById(service.id);
} catch (error) {
console.error(`Error updating service to ${newStatus}:`, error);
alert('Failed to update service. Please try again.');
}
}
}
// FORMAT DATE USING DATE-FNS
function formatDate(dateStr: string | undefined): string {
if (!dateStr) return 'N/A';
try {
const date = parseISO(dateStr);
return format(date, 'MMMM d, yyyy');
} catch (error) {
console.error('Error formatting date:', error);
return 'Invalid Date';
}
}
// FORMAT TIME USING DATE-FNS
function formatTime(dateTimeStr: string | undefined): string {
if (!dateTimeStr) return 'N/A';
try {
const date = parseISO(dateTimeStr);
return format(date, 'h:mm aa');
} catch (error) {
console.error('Error formatting time:', error);
return 'Invalid Time';
}
}
</script>
<div class="container-fluid p-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<a href="/services" class="btn btn-outline-secondary mb-2">
<i class="bi bi-arrow-left"></i> Back to Services
</a>
<h1 class="mb-0">Service Details</h1>
{#if service && account}
<p class="text-muted">
Account: <a href={`/accounts/${account.id}`}>{account.name}</a>
</p>
{/if}
</div>
{#if (isAdmin || isTeamLeader) && service && service.status !== 'completed' && service.status !== 'cancelled'}
<div>
<a href={`/services/${serviceId}/edit`} class="btn btn-primary">
Edit Service
</a>
</div>
{/if}
</div>
{#if loading}
<div class="text-center p-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
{:else if !service}
<div class="alert alert-danger" role="alert">
Service not found
</div>
{:else}
<!-- Service Details -->
<div class="row">
<!-- Main Info -->
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Service Information</h5>
<span class={`badge ${
service.status === 'completed' ? 'bg-success' :
service.status === 'cancelled' ? 'bg-danger' :
service.status === 'in_progress' ? 'bg-primary' : 'bg-secondary'
}`}>
{service.status.replace('_', ' ')}
</span>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<p class="mb-1 text-muted">Service Date</p>
<p class="mb-3">{formatDate(service.date)}</p>
</div>
<div class="col-md-6">
<p class="mb-1 text-muted">Time Window</p>
<p class="mb-3">{formatTime(service.deadline_start)}
- {formatTime(service.deadline_end)}</p>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<p class="mb-1 text-muted">Account Address</p>
{#if account}
<p class="mb-3">
{account.street_address}<br>
{account.city}, {account.state} {account.zip_code}
</p>
{:else}
<p class="text-muted">Account details not available</p>
{/if}
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<p class="mb-1 text-muted">Team Members</p>
{#if service.team_members && service.team_members.length > 0}
<ul class="list-group">
{#each service.team_members as member (typeof member === 'object' ? member.id : member)}
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>{typeof member === 'object' ? `${member.first_name} ${member.last_name}` : 'Unknown Member'}</strong>
<div class="text-muted small">{typeof member === 'object' ? member.role.replace('_', ' ') : ''}</div>
</div>
{#if typeof member === 'object'}
<div>
<a href={`tel:${member.primary_phone}`}
class="btn btn-sm btn-outline-secondary me-1">
<i class="bi bi-telephone"></i>
Call
</a>
<a href={`mailto:${member.email}`}
class="btn btn-sm btn-outline-secondary">
<i class="bi bi-envelope"></i>
Email
</a>
</div>
{/if}
</div>
</li>
{/each}
</ul>
{:else}
<p class="text-muted">No team members assigned</p>
{/if}
</div>
</div>
{#if service.notes}
<div class="row mb-4">
<div class="col-12">
<p class="mb-1 text-muted">Notes</p>
<div class="p-3 bg-light rounded">
<p class="mb-0">{service.notes}</p>
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
{#if isAdmin || isTeamLeader}
<!-- Status Management -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Status Management</h5>
</div>
<div class="card-body">
{#if service.status === 'scheduled'}
<p>This service is currently scheduled.</p>
<div class="d-grid gap-2">
<button
class="btn btn-primary"
onclick={() => updateStatus('in_progress')}
>
Mark as In Progress
</button>
<button
class="btn btn-success"
onclick={() => updateStatus('completed')}
>
Mark as Completed
</button>
<button
class="btn btn-danger"
onclick={() => updateStatus('cancelled')}
>
Cancel Service
</button>
</div>
{:else if service.status === 'in_progress'}
<p>This service is currently in progress.</p>
<div class="d-grid gap-2">
<button
class="btn btn-success"
onclick={() => updateStatus('completed')}
>
Mark as Completed
</button>
<button
class="btn btn-danger"
onclick={() => updateStatus('cancelled')}
>
Cancel Service
</button>
</div>
{:else if service.status === 'completed'}
<p class="text-success">
<i class="bi bi-check-circle"></i>
This service has been completed.
</p>
<div class="d-grid">
<button
class="btn btn-outline-secondary"
onclick={() => updateStatus('in_progress')}
>
Reopen Service
</button>
</div>
{:else if service.status === 'cancelled'}
<p class="text-danger">
<i class="bi bi-x-circle"></i>
This service has been cancelled.
</p>
<div class="d-grid">
<button
class="btn btn-outline-secondary"
onclick={() => updateStatus('scheduled')}
>
Reschedule Service
</button>
</div>
{/if}
</div>
</div>
{/if}
<!-- Contact Information -->
{#if account}
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Account Contact</h5>
</div>
<div class="card-body">
<p class="mb-1"><strong>{account.contact_first_name} {account.contact_last_name}</strong>
</p>
<p class="mb-3">
<a href={`tel:${account.contact_phone}`}>{account.contact_phone}</a><br>
<a href={`mailto:${account.contact_email}`}>{account.contact_email}</a>
</p>
<div class="d-grid gap-2">
<a href={`tel:${account.contact_phone}`} class="btn btn-outline-primary">
<i class="bi bi-telephone"></i> Call Contact
</a>
<a href={`mailto:${account.contact_email}`} class="btn btn-outline-primary">
<i class="bi bi-envelope"></i> Email Contact
</a>
</div>
</div>
</div>
{/if}
<!-- Quick Actions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Quick Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{#if account}
<a href={`/services/new?account_id=${account.id}`} class="btn btn-outline-primary">
<i class="bi bi-calendar-plus"></i> Schedule Another Service
</a>
<a href={`/accounts/${account.id}`} class="btn btn-outline-secondary">
<i class="bi bi-building"></i> View Account
</a>
{/if}
<a href="/services" class="btn btn-outline-secondary">
<i class="bi bi-list-check"></i> View All Services
</a>
</div>
</div>
</div>
</div>
</div>
{/if}
</div>