This commit is contained in:
339
app/pages/insurance-providers/[provider_id].vue
Normal file
339
app/pages/insurance-providers/[provider_id].vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const providerId = route.params.provider_id as string
|
||||
const toast = useToast()
|
||||
const { $providers } = useNuxtApp()
|
||||
|
||||
const { data, pending, error, refresh } = useProviders(`/providers/${providerId}`)
|
||||
const provider = computed(() => data.value?.data)
|
||||
|
||||
const emails = ref<Record<string, string>>({
|
||||
quotes: '',
|
||||
claims: '',
|
||||
renewals: '',
|
||||
billing: '',
|
||||
support: ''
|
||||
})
|
||||
|
||||
const roles = ['quotes', 'claims', 'renewals', 'billing', 'support']
|
||||
|
||||
const getProviderLabel = computed(() => {
|
||||
if (!provider.value) return ''
|
||||
return provider.value.name || 'Unknown'
|
||||
})
|
||||
|
||||
const getRoleLabel = (role: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
quotes: 'Quotes',
|
||||
claims: 'Claims',
|
||||
renewals: 'Renewals',
|
||||
billing: 'Billing',
|
||||
support: 'Support'
|
||||
}
|
||||
return labels[role] || role
|
||||
}
|
||||
|
||||
// templates and default_templates come directly from provider
|
||||
const templates = computed(() => provider.value?.templates ?? {})
|
||||
const defaultTemplates = computed(() => provider.value?.default_templates ?? {})
|
||||
|
||||
// ── Template upload ──────────────────────────────────────────────────────────
|
||||
const isUploadOpen = ref(false)
|
||||
const uploadFile = ref<File | null>(null)
|
||||
const uploadPolicyType = ref('car')
|
||||
const uploadClientType = ref('natural')
|
||||
const uploading = ref(false)
|
||||
|
||||
async function handleUpload() {
|
||||
if (!uploadFile.value) return
|
||||
uploading.value = true
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', uploadFile.value)
|
||||
formData.append('policy_type', uploadPolicyType.value)
|
||||
formData.append('client_type', uploadClientType.value)
|
||||
|
||||
await $providers(`/providers/${providerId}/templates`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
|
||||
toast.add({ title: 'Template uploaded', color: 'green' })
|
||||
isUploadOpen.value = false
|
||||
uploadFile.value = null
|
||||
await refresh()
|
||||
} catch (e: any) {
|
||||
toast.add({ title: 'Upload failed', description: e?.data?.error ?? e.message, color: 'red' })
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onFileChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement
|
||||
uploadFile.value = input.files?.[0] ?? null
|
||||
}
|
||||
|
||||
async function setDefault(templateId: string, policyType: string, clientType: string) {
|
||||
try {
|
||||
await $providers(`/providers/${providerId}/templates/${templateId}/set-default`, {
|
||||
method: 'POST',
|
||||
body: { policy_type: policyType, client_type: clientType }
|
||||
})
|
||||
toast.add({ title: 'Default template updated', color: 'green' })
|
||||
await refresh()
|
||||
} catch (e: any) {
|
||||
toast.add({ title: 'Failed', description: e?.data?.error ?? e.message, color: 'red' })
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleTemplate(templateId: string, active: boolean, policyType: string, clientType: string) {
|
||||
const path = active ? 'deactivate' : 'activate'
|
||||
try {
|
||||
await $providers(`/providers/${providerId}/templates/${templateId}/${path}`, {
|
||||
method: 'POST',
|
||||
body: { policy_type: policyType, client_type: clientType }
|
||||
})
|
||||
toast.add({ title: `Template ${active ? 'deactivated' : 'activated'}`, color: 'green' })
|
||||
await refresh()
|
||||
} catch (e: any) {
|
||||
toast.add({ title: 'Failed', description: e?.data?.error ?? e.message, color: 'red' })
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleProvider() {
|
||||
const path = provider.value?.active ? 'deactivate' : 'reactivate'
|
||||
try {
|
||||
await $providers(`/providers/${providerId}/${path}`, { method: 'POST' })
|
||||
toast.add({ title: `Provider ${provider.value?.active ? 'deactivated' : 'reactivated'}`, color: 'green' })
|
||||
await refresh()
|
||||
} catch (e: any) {
|
||||
toast.add({ title: 'Failed', description: e?.data?.error ?? e.message, color: 'red' })
|
||||
}
|
||||
}
|
||||
|
||||
const policyTypeItems = [
|
||||
{ label: 'Car', value: 'car' },
|
||||
{ label: 'Life', value: 'life' },
|
||||
{ label: 'Fire', value: 'fire' }
|
||||
]
|
||||
|
||||
const clientTypeItems = [
|
||||
{ label: 'Natural', value: 'natural' },
|
||||
{ label: 'Jurídico', value: 'juridico' }
|
||||
]
|
||||
|
||||
const clientTypeLabel = (ct: string) =>
|
||||
ct === 'natural' ? 'Natural' : ct === 'juridico' ? 'Jurídico' : ct
|
||||
|
||||
const clientTypeColor = (ct: string) =>
|
||||
ct === 'natural' ? 'blue' : 'purple'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8 space-y-8 bg-gray-50 min-h-screen">
|
||||
<NuxtLink to="/providers">
|
||||
<UButton icon="i-heroicons-arrow-left" color="gray" variant="ghost">Back to Providers</UButton>
|
||||
</NuxtLink>
|
||||
|
||||
<UAlert v-if="error" color="red" variant="soft" title="Failed to load provider" :description="error.message" />
|
||||
|
||||
<div v-else-if="pending" class="space-y-4">
|
||||
<UCard v-for="n in 2" :key="n"><div class="h-32 animate-pulse bg-gray-200 rounded" /></UCard>
|
||||
</div>
|
||||
|
||||
<template v-else-if="provider">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge :color="provider.active ? 'green' : 'gray'" variant="soft">
|
||||
{{ provider.active ? 'Active' : 'Inactive' }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<h1 class="text-2xl font-semibold text-[var(--text-primary)]">{{ provider.name }}</h1>
|
||||
<p class="text-gray-500 text-sm font-mono">{{ provider.provider_id }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<UButton icon="i-heroicons-arrow-path" color="gray" variant="soft" :loading="pending" @click="refresh()" />
|
||||
<UButton
|
||||
:icon="provider.active ? 'i-heroicons-pause' : 'i-heroicons-play'"
|
||||
:color="provider.active ? 'red' : 'green'"
|
||||
variant="soft"
|
||||
@click="toggleProvider"
|
||||
>
|
||||
{{ provider.active ? 'Deactivate' : 'Reactivate' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<p class="font-semibold text-[var(--text-primary)] flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-building-office" class="w-4 h-4" /> Provider Details
|
||||
</p>
|
||||
</template>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm">
|
||||
<div><p class="text-gray-500 text-xs">Email</p><p class="font-medium">{{ provider.email }}</p></div>
|
||||
<div><p class="text-gray-500 text-xs">Phone</p><p>{{ provider.phone ?? '—' }}</p></div>
|
||||
<div><p class="text-gray-500 text-xs">Contact</p><p>{{ provider.contact_name ?? '—' }}</p></div>
|
||||
<div><p class="text-gray-500 text-xs">RUC</p><p class="font-mono">{{ provider.ruc ?? '—' }}</p></div>
|
||||
<div class="col-span-2"><p class="text-gray-500 text-xs">Address</p><p>{{ provider.address ?? '—' }}</p></div>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<p class="font-semibold text-[var(--text-primary)] flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-envelope" class="w-4 h-4" />
|
||||
Outbound email roles
|
||||
</p>
|
||||
</template>
|
||||
<p class="mb-4 text-xs text-gray-500">
|
||||
Stored in this browser for demo — sync to API later. Workflows (quotes, claims, renewals) resolve recipients
|
||||
from these slots.
|
||||
</p>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<UFormField v-for="role in roles" :key="role" :label="getRoleLabel(role)">
|
||||
<UInput v-model="emails[role]" type="email" placeholder="name@carrier.com" class="w-full" />
|
||||
</UFormField>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<p class="font-semibold text-[var(--text-primary)] flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-document" class="w-4 h-4" /> Solicitation Templates
|
||||
</p>
|
||||
<UButton icon="i-heroicons-arrow-up-tray" color="primary" size="sm" @click="isUploadOpen = true">
|
||||
Upload Template
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="Object.keys(templates).length === 0" class="text-center py-10 text-gray-400">
|
||||
<UIcon name="i-heroicons-document-plus" class="w-10 h-10 mx-auto mb-2" />
|
||||
<p class="text-sm">No templates yet. Upload a provider PDF form.</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-8">
|
||||
<div v-for="(clientMap, policyType) in templates" :key="policyType">
|
||||
<p class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-4">
|
||||
{{ String(policyType) }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-6 pl-2">
|
||||
<div v-for="(tmplList, clientType) in clientMap" :key="clientType">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<UBadge :color="clientTypeColor(String(clientType))" variant="soft" size="sm">
|
||||
{{ clientTypeLabel(String(clientType)) }}
|
||||
</UBadge>
|
||||
<span class="text-xs text-gray-400">{{ tmplList?.length ?? 0 }} template(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="t in tmplList"
|
||||
:key="t.template_id"
|
||||
class="flex items-center justify-between p-3 border rounded-lg text-sm"
|
||||
:class="t.active ? 'border-gray-200 bg-[var(--surface)]' : 'border-gray-100 bg-gray-50 opacity-60'"
|
||||
>
|
||||
<div class="space-y-0.5 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="font-mono text-xs text-gray-500">{{ t.template_id?.slice(0, 8) }}...</span>
|
||||
<UBadge
|
||||
v-if="defaultTemplates?.[policyType]?.[clientType] === t.template_id"
|
||||
color="blue" variant="soft" size="xs"
|
||||
>
|
||||
Default
|
||||
</UBadge>
|
||||
<UBadge :color="t.active ? 'green' : 'gray'" variant="soft" size="xs">
|
||||
{{ t.active ? 'Active' : 'Inactive' }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 font-mono truncate">{{ t.s3_key }}</p>
|
||||
<p class="text-xs text-gray-400">v{{ t.version }} · {{ t.fields?.length ?? 0 }} fields</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 flex-shrink-0">
|
||||
<UButton
|
||||
size="xs" color="gray" variant="soft"
|
||||
:disabled="defaultTemplates?.[policyType]?.[clientType] === t.template_id"
|
||||
@click="setDefault(t.template_id, String(policyType), String(clientType))"
|
||||
>
|
||||
Set Default
|
||||
</UButton>
|
||||
<UButton
|
||||
size="xs" :color="t.active ? 'red' : 'green'" variant="ghost"
|
||||
@click="toggleTemplate(t.template_id, t.active, String(policyType), String(clientType))"
|
||||
>
|
||||
{{ t.active ? 'Deactivate' : 'Activate' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<USlideover v-model:open="isUploadOpen" side="right">
|
||||
<template #content>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex justify-between items-center p-6 border-b">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-[var(--text-primary)]">Upload Template</h2>
|
||||
<p class="text-sm text-gray-500">Upload a fillable PDF solicitation form</p>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" color="gray" variant="ghost" @click="isUploadOpen = false" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-6 space-y-6">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UFormField label="Policy Type" required>
|
||||
<USelect v-model="uploadPolicyType" :items="policyTypeItems" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Client Type" required>
|
||||
<USelect v-model="uploadClientType" :items="clientTypeItems" class="w-full" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<UFormField label="PDF Template File" required>
|
||||
<div
|
||||
class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-primary-400 transition-colors cursor-pointer"
|
||||
@click="($refs.fileInput as HTMLInputElement).click()"
|
||||
>
|
||||
<UIcon name="i-heroicons-document-arrow-up" class="w-10 h-10 mx-auto mb-2 text-gray-400" />
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ uploadFile ? uploadFile.name : 'Click to select a PDF file' }}
|
||||
</p>
|
||||
<p v-if="uploadFile" class="text-xs text-gray-400 mt-1">
|
||||
{{ (uploadFile.size / 1024).toFixed(1) }} KB
|
||||
</p>
|
||||
</div>
|
||||
<input ref="fileInput" type="file" accept=".pdf" class="hidden" @change="onFileChange" />
|
||||
</UFormField>
|
||||
|
||||
<UAlert
|
||||
color="blue" variant="soft" icon="i-heroicons-information-circle"
|
||||
description="The PDF must be an AcroForm (fillable PDF). Fields will be auto-discovered after upload."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="p-6 border-t flex justify-end gap-3">
|
||||
<UButton color="gray" variant="soft" @click="isUploadOpen = false">Cancel</UButton>
|
||||
<UButton
|
||||
color="primary" icon="i-heroicons-arrow-up-tray"
|
||||
:loading="uploading" :disabled="!uploadFile"
|
||||
@click="handleUpload"
|
||||
>
|
||||
Upload
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</USlideover>
|
||||
</div>
|
||||
</template>
|
||||
81
app/pages/insurance-providers/index.vue
Normal file
81
app/pages/insurance-providers/index.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<script setup lang="ts">
|
||||
const search = ref('')
|
||||
const { data, pending, refresh } = useProviders('/providers')
|
||||
|
||||
// Ensure data is refreshed when page loads
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
const providers = computed(() => {
|
||||
const list = data.value?.data ?? []
|
||||
if (!search.value) return list
|
||||
return list.filter((p: any) =>
|
||||
`${p.name} ${p.email} ${p.contact_name}`.toLowerCase().includes(search.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8 space-y-8 bg-gray-50 min-h-screen">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-[var(--text-primary)]">Providers</h1>
|
||||
<p class="text-[13px] text-[var(--text-muted)]">Insurance carrier management</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<UBadge color="gray" variant="soft" size="lg">{{ providers.length }} providers</UBadge>
|
||||
<NuxtLink to="/providers/new">
|
||||
<UButton icon="i-heroicons-plus" color="primary">New Provider</UButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<UInput v-model="search" icon="i-heroicons-magnifying-glass" placeholder="Search providers..." class="w-72" />
|
||||
<UButton icon="i-heroicons-arrow-path" color="gray" variant="soft" :loading="pending" @click="refresh()">Refresh</UButton>
|
||||
</div>
|
||||
|
||||
<div v-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>
|
||||
|
||||
<div v-else class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<NuxtLink v-for="p in providers" :key="p.provider_id" :to="`/providers/${p.provider_id}`">
|
||||
<UCard class="hover:shadow-md transition-shadow cursor-pointer h-full">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-start">
|
||||
<p class="font-semibold text-[var(--text-primary)] text-lg">{{ p.name }}</p>
|
||||
<UBadge :color="p.active ? 'green' : 'red'" variant="soft" size="xs">
|
||||
{{ p.active ? 'Active' : 'Inactive' }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<UIcon name="i-heroicons-envelope" class="w-4 h-4" />
|
||||
<span>{{ p.email }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<UIcon name="i-heroicons-phone" class="w-4 h-4" />
|
||||
<span>{{ p.phone ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<UIcon name="i-heroicons-user" class="w-4 h-4" />
|
||||
<span>{{ p.contact_name ?? '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center pt-2 border-t text-xs text-gray-400">
|
||||
<span>RUC: {{ p.ruc ?? '—' }}</span>
|
||||
<UIcon name="i-heroicons-chevron-right" class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</NuxtLink>
|
||||
|
||||
<div v-if="providers.length === 0" class="col-span-3 text-center py-16 text-gray-400">
|
||||
<UIcon name="i-heroicons-building-office" class="w-12 h-12 mx-auto mb-4" />
|
||||
<p class="text-lg font-medium">No providers found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
138
app/pages/insurance-providers/new.vue
Normal file
138
app/pages/insurance-providers/new.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
const submitting = ref(false)
|
||||
const toast = useToast()
|
||||
const { $providers } = useNuxtApp()
|
||||
|
||||
const form = ref({
|
||||
provider_id: '', name: '', email: '', phone: '', contact_name: '', ruc: '', address: ''
|
||||
})
|
||||
|
||||
const isValid = computed(() => form.value.provider_id && form.value.name && form.value.email)
|
||||
|
||||
async function submit() {
|
||||
submitting.value = true
|
||||
try {
|
||||
const data = await $providers('/providers', { method: 'POST', body: form.value }) as any
|
||||
toast.add({ title: 'Provider created', color: 'green' })
|
||||
router.push(`/providers/${data.provider_id}`)
|
||||
} catch (e: any) {
|
||||
toast.add({ title: 'Failed to create provider', description: e?.data?.error ?? e.message, color: 'red' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8 space-y-8 bg-gray-50 min-h-screen">
|
||||
<div class="flex items-center gap-4">
|
||||
<NuxtLink to="/providers">
|
||||
<UButton icon="i-heroicons-arrow-left" color="gray" variant="ghost">Back</UButton>
|
||||
</NuxtLink>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-[var(--text-primary)]">New Provider</h1>
|
||||
<p class="text-[13px] text-[var(--text-muted)]">Register a new insurance carrier</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UCard class="max-w-2xl">
|
||||
<template #header>
|
||||
<p class="font-semibold text-[var(--text-primary)] flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-building-office" class="w-4 h-4" /> Provider Information
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UFormField label="Provider ID" required>
|
||||
<UInput v-model="form.provider_id" placeholder="seguros-abc" class="w-full" />
|
||||
<p class="text-xs text-gray-500 mt-1">Alphanumeric identifier (letters and numbers only)</p>
|
||||
</UFormField>
|
||||
<UFormField label="Company Name" required class="col-span-2">
|
||||
<UInput v-model="form.name" placeholder="Seguros Panama S.A." class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Email" required>
|
||||
<UInput v-model="form.email" type="email" placeholder="cotizaciones@seguros.com" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Phone">
|
||||
<UInput v-model="form.phone" placeholder="+507 300-0000" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Contact Name">
|
||||
<UInput v-model="form.contact_name" placeholder="María García" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="RUC">
|
||||
<UInput v-model="form.ruc" placeholder="1234567-1-123456" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="Address" class="col-span-2">
|
||||
<UInput v-model="form.address" placeholder="Av. Balboa, Panama City" class="w-full" />
|
||||
</UFormField>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-3">
|
||||
<NuxtLink to="/providers"><UButton color="gray" variant="soft">Cancel</UButton></NuxtLink>
|
||||
<UButton color="primary" icon="i-heroicons-check" :loading="submitting" :disabled="!isValid" @click="submit">
|
||||
Create Provider
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Prevent width expansion when dropdowns open */
|
||||
:deep(.grid) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.UFormField) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.UInput) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.USelect) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Prevent dropdown menus from expanding container */
|
||||
:deep([role="listbox"]) {
|
||||
position: fixed !important;
|
||||
z-index: 50;
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
/* Ensure form fields don't cause expansion */
|
||||
:deep(.space-y-4) {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Prevent any element from expanding beyond container */
|
||||
:deep(*) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Specific fix for dropdown positioning */
|
||||
:deep(.USelectMenuPopover) {
|
||||
position: fixed !important;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Ensure form stability */
|
||||
:deep(.UCard) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user