This commit is contained in:
Haim Kortovich
2026-03-05 11:36:23 -05:00
commit 7e8025700b
19 changed files with 13483 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
const route = useRoute()
const { data, error, pending } = useCustomer(route.params.id)
</script>
<template>
<div class="p-8 bg-slate-100 min-h-screen">
<!-- Loading -->
<div v-if="pending" class="max-w-3xl">
<UCard class="bg-white shadow-sm border border-slate-200">
<div class="h-48 animate-pulse bg-slate-200 rounded" />
</UCard>
</div>
<!-- Error -->
<UAlert
v-else-if="error"
color="red"
variant="soft"
title="Failed to load customer"
:description="error.message"
/>
<!-- Customer View -->
<div v-else-if="data" class="max-w-4xl space-y-6">
<!-- Header Card -->
<UCard class="bg-white border border-slate-200 shadow-sm rounded-xl">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<UAvatar size="xl" :alt="data.first_name" />
<div>
<h1 class="text-2xl font-semibold text-slate-900">
{{ data.first_name }} {{ data.last_name }}
</h1>
<p class="text-slate-500 text-sm">
{{ data.email }}
</p>
</div>
</div>
</div>
</UCard>
<!-- Details Card -->
<UCard class="bg-white border border-slate-200 shadow-sm rounded-xl">
<h2 class="text-lg font-semibold text-slate-800 mb-4">
Personal Information
</h2>
<div class="grid grid-cols-2 gap-6 text-sm">
<div>
<div class="text-slate-500">Birth Date</div>
<div class="text-slate-800 font-medium">
{{ data.birth_date }}
</div>
</div>
<div>
<div class="text-slate-500">Gender</div>
<div class="text-slate-800 font-medium capitalize">
{{ data.gender }}
</div>
</div>
<div>
<div class="text-slate-500">Phone</div>
<div class="text-slate-800 font-medium">
{{ data.phone }}
</div>
</div>
<div>
<div class="text-slate-500">Customer ID</div>
<div class="text-slate-800 font-mono text-xs">
{{ data.id }}
</div>
</div>
</div>
</UCard>
<!-- Placeholder for Policies -->
<UCard class="bg-white border border-slate-200 shadow-sm rounded-xl">
<h2 class="text-lg font-semibold text-slate-800 mb-4">
Policies
</h2>
<div class="text-slate-500 text-sm">
No policies yet.
</div>
</UCard>
</div>
</div>
</template>

View File

@@ -0,0 +1,65 @@
<script setup lang="ts">
const { data, error, pending } = useCustomer('/')
const search = ref('')
const filtered = computed(() => {
if (!data.value) return []
if (!search.value) return data.value
return data.value.filter((c: any) =>
`${c.first_name} ${c.last_name} ${c.email}`
.toLowerCase()
.includes(search.value.toLowerCase())
)
})
const total = computed(() => data.value?.length ?? 0)
</script>
<template>
<div class="p-8 space-y-8 bg-gray-50 min-h-screen">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="text-3xl text-slate-900 font-bold">Customers</h1>
<p class="text-gray-500 text-sm">Customer Relationship Management</p>
</div>
<UButton icon="i-heroicons-plus" color="primary" to="/customers/new">
New Customer
</UButton>
</div>
<!-- Error -->
<UAlert
v-if="error"
color="red"
variant="soft"
title="Failed to load customers"
:description="error.message"
/>
<!-- Loading -->
<div
v-else-if="pending"
class="grid gap-6 md:grid-cols-2 lg:grid-cols-3"
>
<UCard v-for="n in 6" :key="n">
<div class="h-32 animate-pulse bg-gray-200 rounded" />
</UCard>
</div>
<!-- Customer Grid -->
<div
v-else
class="grid gap-6 md:grid-cols-2 lg:grid-cols-3"
>
<CustomerCard
v-for="customer in filtered"
:key="customer.id"
:customer="customer"
/>
</div>
</div>
</template>

126
app/pages/customers/new.vue Normal file
View File

@@ -0,0 +1,126 @@
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const schema = z.object({
first_name: z.string().min(1, 'First name is required'),
last_name: z.string().min(1, 'Last name is required'),
email: z.string().email('Invalid email'),
phone: z.string().min(1, 'Phone is required'),
birth_date: z.string().min(1, 'Birth date is required'),
gender: z.enum(['male', 'female'])
})
type Schema = z.output<typeof schema>
const state = reactive<Partial<Schema>>({
first_name: '',
last_name: '',
email: '',
phone: '',
birth_date: '',
gender: 'male'
})
const toast = useToast()
const router = useRouter()
async function onSubmit(event: FormSubmitEvent<Schema>) {
try {
await useCustomer('/', {
method: 'POST',
body: event.data
})
toast.add({
title: 'Customer created',
description: 'The customer was successfully created.',
color: 'success'
})
router.push('/customers')
} catch (err: any) {
toast.add({
title: 'Error',
description: err?.message || 'Failed to create customer',
color: 'error'
})
}
}
</script>
<template>
<div class="min-h-screen bg-gray-50 p-10">
<div class="max-w-5xl mx-auto space-y-8">
<!-- Header -->
<div>
<h1 class="text-3xl font-bold text-gray-900">New Customer</h1>
<p class="text-gray-500 mt-1">Create a new customer profile</p>
</div>
<!-- Form Card -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
<UForm
:schema="schema"
:state="state"
class="space-y-6"
@submit="onSubmit"
>
<!-- Grid -->
<div class="grid md:grid-cols-2 gap-6">
<UFormField label="First Name" name="first_name">
<UInput v-model="state.first_name" />
</UFormField>
<UFormField label="Last Name" name="last_name">
<UInput v-model="state.last_name" />
</UFormField>
<UFormField label="Email" name="email">
<UInput v-model="state.email" />
</UFormField>
<UFormField label="Phone" name="phone">
<UInput v-model="state.phone" />
</UFormField>
<UFormField label="Birth Date" name="birth_date">
<UInput v-model="state.birth_date" type="date" />
</UFormField>
<UFormField label="Gender" name="gender">
<USelect
v-model="state.gender"
:items="[
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' }
]"
/>
</UFormField>
</div>
<!-- Actions -->
<div class="flex justify-end gap-4 pt-4">
<UButton
color="neutral"
variant="ghost"
@click="$router.push('/customers')"
>
Cancel
</UButton>
<UButton type="submit" color="primary">
Create Customer
</UButton>
</div>
</UForm>
</div>
</div>
</div>
</template>

11
app/pages/index.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<UContainer class="py-10">
<UCard>
<template #header>
<h1 class="text-2xl font-bold">
</h1>
</template>
</UCard>
</UContainer>
</template>