Test commit to new origin
This commit is contained in:
parent
c1ac92efbf
commit
c4a78b4632
1127
package-lock.json
generated
1127
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,8 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build"
|
"build": "vite build",
|
||||||
|
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inertiajs/vue3": "2.0",
|
"@inertiajs/vue3": "2.0",
|
||||||
|
|
@ -11,14 +12,17 @@
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tailwindcss/postcss": "^4.1.16",
|
"@tailwindcss/postcss": "^4.1.16",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"laravel-vite-plugin": "^2.0.1",
|
"laravel-vite-plugin": "^2.0.1",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.32",
|
||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
"vue": "^3.3.13"
|
"vue": "^3.3.13",
|
||||||
|
"vue-tsc": "^3.1.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref, useAttrs } from "vue";
|
||||||
import { Button } from "@/Components/ui/button";
|
import { Button } from "@/Components/ui/button";
|
||||||
import { Calendar } from "@/Components/ui/calendar";
|
import { Calendar } from "@/Components/ui/calendar";
|
||||||
import {
|
import {
|
||||||
|
|
@ -13,6 +13,8 @@ import { format } from "date-fns";
|
||||||
import { sl } from "date-fns/locale";
|
import { sl } from "date-fns/locale";
|
||||||
import { CalendarDate, parseDate } from "@internationalized/date";
|
import { CalendarDate, parseDate } from "@internationalized/date";
|
||||||
|
|
||||||
|
defineOptions({ inheritAttrs: false });
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Date, String, null],
|
type: [Date, String, null],
|
||||||
|
|
@ -42,6 +44,13 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue"]);
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const forwardedAttrs = computed(() => {
|
||||||
|
const { class: _class, id: _id, ...rest } = attrs;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
const controlId = computed(() => attrs.id ?? props.id);
|
||||||
|
|
||||||
// Convert string/Date to CalendarDate
|
// Convert string/Date to CalendarDate
|
||||||
const toCalendarDate = (value) => {
|
const toCalendarDate = (value) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
|
@ -115,13 +124,15 @@ const open = ref(false);
|
||||||
<Popover v-model:open="open">
|
<Popover v-model:open="open">
|
||||||
<PopoverTrigger as-child>
|
<PopoverTrigger as-child>
|
||||||
<Button
|
<Button
|
||||||
:id="id"
|
v-bind="forwardedAttrs"
|
||||||
|
:id="controlId"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'w-full justify-start text-left font-normal',
|
'w-full justify-start text-left font-normal',
|
||||||
!calendarDate && 'text-muted-foreground',
|
!calendarDate && 'text-muted-foreground',
|
||||||
error && 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
error && 'border-red-500 focus:border-red-500 focus:ring-red-500',
|
||||||
|
attrs.class
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
|
|
||||||
70
resources/js/Components/Layouts/NavMain.vue
Normal file
70
resources/js/Components/Layouts/NavMain.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { LucideIcon } from "lucide-vue-next";
|
||||||
|
import { ChevronRight } from "lucide-vue-next";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "@/Components/ui/collapsible";
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon?: LucideIcon;
|
||||||
|
isActive?: boolean;
|
||||||
|
items?: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
<Collapsible
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.title"
|
||||||
|
as-child
|
||||||
|
:default-open="item.isActive"
|
||||||
|
class="group/collapsible"
|
||||||
|
>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger as-child>
|
||||||
|
<SidebarMenuButton :tooltip="item.title">
|
||||||
|
<component :is="item.icon" v-if="item.icon" />
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
<ChevronRight
|
||||||
|
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
||||||
|
/>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
|
||||||
|
<SidebarMenuSubButton as-child>
|
||||||
|
<a :href="subItem.url">
|
||||||
|
<span>{{ subItem.title }}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
</template>
|
||||||
|
|
@ -24,7 +24,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
<Card class="p-2" v-for="address in person.addresses" :key="address.id">
|
<Card class="p-2 gap-1" v-for="address in person.addresses" :key="address.id">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span
|
<span
|
||||||
|
|
@ -61,7 +61,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm font-medium text-gray-900 leading-relaxed">
|
<p class="text-sm font-medium text-gray-900 leading-relaxed p-1">
|
||||||
{{
|
{{
|
||||||
address.post_code && address.city
|
address.post_code && address.city
|
||||||
? `${address.address}, ${address.post_code} ${address.city}`
|
? `${address.address}, ${address.post_code} ${address.city}`
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
<template v-if="getEmails(person).length">
|
<template v-if="getEmails(person).length">
|
||||||
<Card class="p-2" v-for="(email, idx) in getEmails(person)" :key="idx">
|
<Card class="p-2 gap-1" v-for="(email, idx) in getEmails(person)" :key="idx">
|
||||||
<div class="flex items-center justify-between mb-2" v-if="edit">
|
<div class="flex items-center justify-between mb-2" v-if="edit">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span
|
<span
|
||||||
|
|
@ -68,6 +68,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-1">
|
||||||
<p class="text-sm font-medium text-gray-900 leading-relaxed">
|
<p class="text-sm font-medium text-gray-900 leading-relaxed">
|
||||||
{{ email?.value || email?.email || email?.address || "-" }}
|
{{ email?.value || email?.email || email?.address || "-" }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -77,6 +78,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
>
|
>
|
||||||
{{ email.note }}
|
{{ email.note }}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const handleSms = (phone) => emit("sms", phone);
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
<template v-if="getPhones(person).length">
|
<template v-if="getPhones(person).length">
|
||||||
<Card class="p-2" v-for="phone in getPhones(person)" :key="phone.id">
|
<Card class="p-2 gap-1" v-for="phone in getPhones(person)" :key="phone.id">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div class="flex items-center justify-between mb-2">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span
|
<span
|
||||||
|
|
@ -79,7 +79,9 @@ const handleSms = (phone) => emit("sms", phone);
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm font-medium text-gray-900 leading-relaxed">{{ phone.nu }}</p>
|
<p class="text-sm font-medium text-gray-900 leading-relaxed p-1">
|
||||||
|
{{ phone.nu }}
|
||||||
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,7 @@ import { router, usePage } from "@inertiajs/vue3";
|
||||||
import { useForm, Field as FormField } from "vee-validate";
|
import { useForm, Field as FormField } from "vee-validate";
|
||||||
import { toTypedSchema } from "@vee-validate/zod";
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import {
|
import { FormControl, FormItem, FormLabel, FormMessage } from "@/Components/ui/form";
|
||||||
FormControl,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/Components/ui/form";
|
|
||||||
import { Input } from "@/Components/ui/input";
|
import { Input } from "@/Components/ui/input";
|
||||||
import { Textarea } from "@/Components/ui/textarea";
|
import { Textarea } from "@/Components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
|
|
@ -158,8 +153,7 @@ const formSchema = toTypedSchema(
|
||||||
(val) => {
|
(val) => {
|
||||||
const encoding = isGsm7(val) ? "GSM-7" : "UCS-2";
|
const encoding = isGsm7(val) ? "GSM-7" : "UCS-2";
|
||||||
const maxAllowed = encoding === "GSM-7" ? 640 : 320;
|
const maxAllowed = encoding === "GSM-7" ? 640 : 320;
|
||||||
const count =
|
const count = encoding === "GSM-7" ? gsm7Length(val) : ucs2Length(val);
|
||||||
encoding === "GSM-7" ? gsm7Length(val) : ucs2Length(val);
|
|
||||||
return count <= maxAllowed;
|
return count <= maxAllowed;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -196,9 +190,7 @@ const sendersForSelectedProfile = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const smsEncoding = computed(() =>
|
const smsEncoding = computed(() => (isGsm7(form.values.message) ? "GSM-7" : "UCS-2"));
|
||||||
isGsm7(form.values.message) ? "GSM-7" : "UCS-2"
|
|
||||||
);
|
|
||||||
|
|
||||||
const charCount = computed(() =>
|
const charCount = computed(() =>
|
||||||
smsEncoding.value === "GSM-7"
|
smsEncoding.value === "GSM-7"
|
||||||
|
|
@ -222,13 +214,9 @@ const segments = computed(() => {
|
||||||
|
|
||||||
const creditsNeeded = computed(() => segments.value);
|
const creditsNeeded = computed(() => segments.value);
|
||||||
|
|
||||||
const maxAllowed = computed(() =>
|
const maxAllowed = computed(() => (smsEncoding.value === "GSM-7" ? 640 : 320));
|
||||||
smsEncoding.value === "GSM-7" ? 640 : 320
|
|
||||||
);
|
|
||||||
|
|
||||||
const remaining = computed(() =>
|
const remaining = computed(() => Math.max(0, maxAllowed.value - charCount.value));
|
||||||
Math.max(0, maxAllowed.value - charCount.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Truncate message if exceeds limit
|
// Truncate message if exceeds limit
|
||||||
watch(
|
watch(
|
||||||
|
|
@ -236,10 +224,7 @@ watch(
|
||||||
(val) => {
|
(val) => {
|
||||||
const limit = maxAllowed.value;
|
const limit = maxAllowed.value;
|
||||||
if (charCount.value > limit) {
|
if (charCount.value > limit) {
|
||||||
form.setFieldValue(
|
form.setFieldValue("message", truncateToLimit(val, limit, smsEncoding.value));
|
||||||
"message",
|
|
||||||
truncateToLimit(val, limit, smsEncoding.value)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -299,14 +284,10 @@ const buildVarsFromSelectedContract = () => {
|
||||||
type: c.account.type,
|
type: c.account.type,
|
||||||
initial_amount:
|
initial_amount:
|
||||||
c.account.initial_amount ??
|
c.account.initial_amount ??
|
||||||
(c.account.initial_amount_raw
|
(c.account.initial_amount_raw ? formatEu(c.account.initial_amount_raw) : null),
|
||||||
? formatEu(c.account.initial_amount_raw)
|
|
||||||
: null),
|
|
||||||
balance_amount:
|
balance_amount:
|
||||||
c.account.balance_amount ??
|
c.account.balance_amount ??
|
||||||
(c.account.balance_amount_raw
|
(c.account.balance_amount_raw ? formatEu(c.account.balance_amount_raw) : null),
|
||||||
? formatEu(c.account.balance_amount_raw)
|
|
||||||
: null),
|
|
||||||
initial_amount_raw: c.account.initial_amount_raw ?? null,
|
initial_amount_raw: c.account.initial_amount_raw ?? null,
|
||||||
balance_amount_raw: c.account.balance_amount_raw ?? null,
|
balance_amount_raw: c.account.balance_amount_raw ?? null,
|
||||||
};
|
};
|
||||||
|
|
@ -497,11 +478,7 @@ const open = computed({
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem :value="null">—</SelectItem>
|
<SelectItem :value="null">—</SelectItem>
|
||||||
<SelectItem
|
<SelectItem v-for="p in pageSmsProfiles" :key="p.id" :value="p.id">
|
||||||
v-for="p in pageSmsProfiles"
|
|
||||||
:key="p.id"
|
|
||||||
:value="p.id"
|
|
||||||
>
|
|
||||||
{{ p.name || "Profil #" + p.id }}
|
{{ p.name || "Profil #" + p.id }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -546,11 +523,7 @@ const open = computed({
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem :value="null">—</SelectItem>
|
<SelectItem :value="null">—</SelectItem>
|
||||||
<SelectItem
|
<SelectItem v-for="c in contractsForCase" :key="c.uuid" :value="c.uuid">
|
||||||
v-for="c in contractsForCase"
|
|
||||||
:key="c.uuid"
|
|
||||||
:value="c.uuid"
|
|
||||||
>
|
|
||||||
{{ c.reference || c.uuid }}
|
{{ c.reference || c.uuid }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -574,11 +547,7 @@ const open = computed({
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem :value="null">—</SelectItem>
|
<SelectItem :value="null">—</SelectItem>
|
||||||
<SelectItem
|
<SelectItem v-for="t in pageSmsTemplates" :key="t.id" :value="t.id">
|
||||||
v-for="t in pageSmsTemplates"
|
|
||||||
:key="t.id"
|
|
||||||
:value="t.id"
|
|
||||||
>
|
|
||||||
{{ t.name || "Predloga #" + t.id }}
|
{{ t.name || "Predloga #" + t.id }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -626,14 +595,13 @@ const open = computed({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-[11px] text-gray-500 leading-snug">
|
<p class="text-[11px] text-gray-500 leading-snug">
|
||||||
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo
|
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo znake, ki
|
||||||
znake, ki ne zahtevajo enkodiranja. Če npr. želite pošiljati
|
ne zahtevajo enkodiranja. Če npr. želite pošiljati šumnike, ki niso del
|
||||||
šumnike, ki niso del 7-bitne abecede GSM, morate uporabiti Unicode
|
7-bitne abecede GSM, morate uporabiti Unicode enkodiranje (UCS‑2). V tem
|
||||||
enkodiranje (UCS‑2). V tem primeru je največja dolžina enega SMS
|
primeru je največja dolžina enega SMS sporočila 70 znakov (pri daljših
|
||||||
sporočila 70 znakov (pri daljših sporočilih 67 znakov na del),
|
sporočilih 67 znakov na del), medtem ko je pri GSM‑7 160 znakov (pri daljših
|
||||||
medtem ko je pri GSM‑7 160 znakov (pri daljših sporočilih 153
|
sporočilih 153 znakov na del). Razširjeni znaki (^{{ "{" }}}}\\[]~| in €)
|
||||||
znakov na del). Razširjeni znaki (^{{ "{" }}}}\\[]~| in €) štejejo
|
štejejo dvojno. Največja dovoljena dolžina po ponudniku: 640 (GSM‑7) oziroma
|
||||||
dvojno. Največja dovoljena dolžina po ponudniku: 640 (GSM‑7) oziroma
|
|
||||||
320 (UCS‑2) znakov.
|
320 (UCS‑2) znakov.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -641,10 +609,7 @@ const open = computed({
|
||||||
<FormField v-slot="{ value, handleChange }" name="delivery_report">
|
<FormField v-slot="{ value, handleChange }" name="delivery_report">
|
||||||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch :model-value="value" @update:model-value="handleChange" />
|
||||||
:model-value="value"
|
|
||||||
@update:model-value="handleChange"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div class="space-y-1 leading-none">
|
<div class="space-y-1 leading-none">
|
||||||
<FormLabel>Zahtevaj poročilo o dostavi</FormLabel>
|
<FormLabel>Zahtevaj poročilo o dostavi</FormLabel>
|
||||||
|
|
@ -657,10 +622,7 @@ const open = computed({
|
||||||
<Button variant="outline" @click="closeSmsDialog" :disabled="processing">
|
<Button variant="outline" @click="closeSmsDialog" :disabled="processing">
|
||||||
Prekliči
|
Prekliči
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button @click="onSubmit" :disabled="processing || !form.values.message">
|
||||||
@click="onSubmit"
|
|
||||||
:disabled="processing || !form.values.message"
|
|
||||||
>
|
|
||||||
Pošlji
|
Pošlji
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
<template v-if="getTRRs(person).length">
|
<template v-if="getTRRs(person).length">
|
||||||
<Card class="p-2" v-for="(acc, idx) in getTRRs(person)" :key="idx">
|
<Card class="p-2 gap-2" v-for="(acc, idx) in getTRRs(person)" :key="idx">
|
||||||
<div class="flex items-center justify-between mb-2" v-if="edit">
|
<div class="flex items-center justify-between mb-2" v-if="edit">
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
178
resources/js/Components/app/ui/layout/AppSidebar.vue
Normal file
178
resources/js/Components/app/ui/layout/AppSidebar.vue
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SidebarProps } from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AudioWaveform,
|
||||||
|
BookOpen,
|
||||||
|
Bot,
|
||||||
|
Command,
|
||||||
|
Frame,
|
||||||
|
GalleryVerticalEnd,
|
||||||
|
Map,
|
||||||
|
PieChart,
|
||||||
|
Settings2,
|
||||||
|
SquareTerminal,
|
||||||
|
} from "lucide-vue-next";
|
||||||
|
import NavMain from "@/Components/app/ui/layout/NavMain.vue";
|
||||||
|
import NavProjects from "@/Components/app/ui/layout/NavProjects.vue";
|
||||||
|
import NavUser from "@/Components/app/ui/layout/NavUser.vue";
|
||||||
|
import TeamSwitcher from "@/Components/app/ui/layout/TeamSwitcher.vue";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarContent,
|
||||||
|
SidebarFooter,
|
||||||
|
SidebarHeader,
|
||||||
|
SidebarRail,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<SidebarProps>(), {
|
||||||
|
collapsible: "icon",
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is sample data.
|
||||||
|
const data = {
|
||||||
|
user: {
|
||||||
|
name: "shadcn",
|
||||||
|
email: "m@example.com",
|
||||||
|
avatar: "/avatars/shadcn.jpg",
|
||||||
|
},
|
||||||
|
teams: [
|
||||||
|
{
|
||||||
|
name: "Acme Inc",
|
||||||
|
logo: GalleryVerticalEnd,
|
||||||
|
plan: "Enterprise",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Acme Corp.",
|
||||||
|
logo: AudioWaveform,
|
||||||
|
plan: "Startup",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Evil Corp.",
|
||||||
|
logo: Command,
|
||||||
|
plan: "Free",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Playground",
|
||||||
|
url: "#",
|
||||||
|
icon: SquareTerminal,
|
||||||
|
isActive: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "History",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Starred",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Models",
|
||||||
|
url: "#",
|
||||||
|
icon: Bot,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Genesis",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Explorer",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Quantum",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Documentation",
|
||||||
|
url: "#",
|
||||||
|
icon: BookOpen,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Introduction",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Get Started",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Tutorials",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Changelog",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Settings",
|
||||||
|
url: "#",
|
||||||
|
icon: Settings2,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "General",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Billing",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Limits",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "Design Engineering",
|
||||||
|
url: "#",
|
||||||
|
icon: Frame,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sales & Marketing",
|
||||||
|
url: "#",
|
||||||
|
icon: PieChart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Travel",
|
||||||
|
url: "#",
|
||||||
|
icon: Map,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Sidebar v-bind="props">
|
||||||
|
<SidebarHeader>
|
||||||
|
<TeamSwitcher :teams="data.teams" />
|
||||||
|
</SidebarHeader>
|
||||||
|
<SidebarContent>
|
||||||
|
<NavMain :items="data.navMain" />
|
||||||
|
<NavProjects :projects="data.projects" />
|
||||||
|
</SidebarContent>
|
||||||
|
<SidebarFooter>
|
||||||
|
<NavUser :user="data.user" />
|
||||||
|
</SidebarFooter>
|
||||||
|
<SidebarRail />
|
||||||
|
</Sidebar>
|
||||||
|
</template>
|
||||||
70
resources/js/Components/app/ui/layout/NavMain.vue
Normal file
70
resources/js/Components/app/ui/layout/NavMain.vue
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { LucideIcon } from "lucide-vue-next";
|
||||||
|
import { ChevronRight } from "lucide-vue-next";
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from "@/Components/ui/collapsible";
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
SidebarMenuSub,
|
||||||
|
SidebarMenuSubButton,
|
||||||
|
SidebarMenuSubItem,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
icon?: LucideIcon;
|
||||||
|
isActive?: boolean;
|
||||||
|
items?: {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SidebarGroup>
|
||||||
|
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
<Collapsible
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.title"
|
||||||
|
as-child
|
||||||
|
:default-open="item.isActive"
|
||||||
|
class="group/collapsible"
|
||||||
|
>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<CollapsibleTrigger as-child>
|
||||||
|
<SidebarMenuButton :tooltip="item.title">
|
||||||
|
<component :is="item.icon" v-if="item.icon" />
|
||||||
|
<span>{{ item.title }}</span>
|
||||||
|
<ChevronRight
|
||||||
|
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
|
||||||
|
/>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<SidebarMenuSub>
|
||||||
|
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
|
||||||
|
<SidebarMenuSubButton as-child>
|
||||||
|
<a :href="subItem.url">
|
||||||
|
<span>{{ subItem.title }}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</Collapsible>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
</template>
|
||||||
80
resources/js/Components/app/ui/layout/NavProjects.vue
Normal file
80
resources/js/Components/app/ui/layout/NavProjects.vue
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { LucideIcon } from "lucide-vue-next";
|
||||||
|
import { Folder, Forward, MoreHorizontal, Trash2 } from "lucide-vue-next";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/Components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupLabel,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuAction,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
projects: {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
}[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SidebarGroup class="group-data-[collapsible=icon]:hidden">
|
||||||
|
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem v-for="item in projects" :key="item.name">
|
||||||
|
<SidebarMenuButton as-child>
|
||||||
|
<a :href="item.url">
|
||||||
|
<component :is="item.icon" />
|
||||||
|
<span>{{ item.name }}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<SidebarMenuAction show-on-hover>
|
||||||
|
<MoreHorizontal />
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</SidebarMenuAction>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
class="w-48 rounded-lg"
|
||||||
|
:side="isMobile ? 'bottom' : 'right'"
|
||||||
|
:align="isMobile ? 'end' : 'start'"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Folder class="text-muted-foreground" />
|
||||||
|
<span>View Project</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Forward class="text-muted-foreground" />
|
||||||
|
<span>Share Project</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Trash2 class="text-muted-foreground" />
|
||||||
|
<span>Delete Project</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<SidebarMenuButton class="text-sidebar-foreground/70">
|
||||||
|
<MoreHorizontal class="text-sidebar-foreground/70" />
|
||||||
|
<span>More</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroup>
|
||||||
|
</template>
|
||||||
108
resources/js/Components/app/ui/layout/NavUser.vue
Normal file
108
resources/js/Components/app/ui/layout/NavUser.vue
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
BadgeCheck,
|
||||||
|
Bell,
|
||||||
|
ChevronsUpDown,
|
||||||
|
CreditCard,
|
||||||
|
LogOut,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-vue-next";
|
||||||
|
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/Components/ui/avatar";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/Components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
avatar: string;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<Avatar class="h-8 w-8 rounded-lg">
|
||||||
|
<AvatarImage :src="user.avatar" :alt="user.name" />
|
||||||
|
<AvatarFallback class="rounded-lg"> CN </AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span class="truncate font-medium">{{ user.name }}</span>
|
||||||
|
<span class="truncate text-xs">{{ user.email }}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown class="ml-auto size-4" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||||
|
:side="isMobile ? 'bottom' : 'right'"
|
||||||
|
align="end"
|
||||||
|
:side-offset="4"
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel class="p-0 font-normal">
|
||||||
|
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
|
<Avatar class="h-8 w-8 rounded-lg">
|
||||||
|
<AvatarImage :src="user.avatar" :alt="user.name" />
|
||||||
|
<AvatarFallback class="rounded-lg"> CN </AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span class="truncate font-semibold">{{ user.name }}</span>
|
||||||
|
<span class="truncate text-xs">{{ user.email }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Sparkles />
|
||||||
|
Upgrade to Pro
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<BadgeCheck />
|
||||||
|
Account
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<CreditCard />
|
||||||
|
Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Bell />
|
||||||
|
Notifications
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<LogOut />
|
||||||
|
Log out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</template>
|
||||||
92
resources/js/Components/app/ui/layout/TeamSwitcher.vue
Normal file
92
resources/js/Components/app/ui/layout/TeamSwitcher.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Component } from "vue";
|
||||||
|
|
||||||
|
import { ChevronsUpDown, Plus } from "lucide-vue-next";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/Components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
|
} from "@/Components/ui/sidebar";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
teams: {
|
||||||
|
name: string;
|
||||||
|
logo: Component;
|
||||||
|
plan: string;
|
||||||
|
}[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { isMobile } = useSidebar();
|
||||||
|
const activeTeam = ref(props.teams[0]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SidebarMenu>
|
||||||
|
<SidebarMenuItem>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<SidebarMenuButton
|
||||||
|
size="lg"
|
||||||
|
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
|
||||||
|
>
|
||||||
|
<component :is="activeTeam.logo" class="size-4" />
|
||||||
|
</div>
|
||||||
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||||
|
<span class="truncate font-medium">
|
||||||
|
{{ activeTeam.name }}
|
||||||
|
</span>
|
||||||
|
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
|
||||||
|
</div>
|
||||||
|
<ChevronsUpDown class="ml-auto" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||||
|
align="start"
|
||||||
|
:side="isMobile ? 'bottom' : 'right'"
|
||||||
|
:side-offset="4"
|
||||||
|
>
|
||||||
|
<DropdownMenuLabel class="text-xs text-muted-foreground">
|
||||||
|
Teams
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
v-for="(team, index) in teams"
|
||||||
|
:key="team.name"
|
||||||
|
class="gap-2 p-2"
|
||||||
|
@click="activeTeam = team"
|
||||||
|
>
|
||||||
|
<div class="flex size-6 items-center justify-center rounded-sm border">
|
||||||
|
<component :is="team.logo" class="size-3.5 shrink-0" />
|
||||||
|
</div>
|
||||||
|
{{ team.name }}
|
||||||
|
<DropdownMenuShortcut>⌘{{ index + 1 }}</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem class="gap-2 p-2">
|
||||||
|
<div
|
||||||
|
class="flex size-6 items-center justify-center rounded-md border bg-transparent"
|
||||||
|
>
|
||||||
|
<Plus class="size-4" />
|
||||||
|
</div>
|
||||||
|
<div class="font-medium text-muted-foreground">Add team</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</template>
|
||||||
22
resources/js/Components/ui/avatar/Avatar.vue
Normal file
22
resources/js/Components/ui/avatar/Avatar.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { AvatarVariants } from "."
|
||||||
|
import { AvatarRoot } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { avatarVariant } from "."
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
size?: AvatarVariants["size"]
|
||||||
|
shape?: AvatarVariants["shape"]
|
||||||
|
}>(), {
|
||||||
|
size: "sm",
|
||||||
|
shape: "circle",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AvatarRoot :class="cn(avatarVariant({ size, shape }), props.class)">
|
||||||
|
<slot />
|
||||||
|
</AvatarRoot>
|
||||||
|
</template>
|
||||||
12
resources/js/Components/ui/avatar/AvatarFallback.vue
Normal file
12
resources/js/Components/ui/avatar/AvatarFallback.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AvatarFallbackProps } from "reka-ui"
|
||||||
|
import { AvatarFallback } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<AvatarFallbackProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AvatarFallback v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</AvatarFallback>
|
||||||
|
</template>
|
||||||
12
resources/js/Components/ui/avatar/AvatarImage.vue
Normal file
12
resources/js/Components/ui/avatar/AvatarImage.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AvatarImageProps } from "reka-ui"
|
||||||
|
import { AvatarImage } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<AvatarImageProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AvatarImage v-bind="props" class="h-full w-full object-cover">
|
||||||
|
<slot />
|
||||||
|
</AvatarImage>
|
||||||
|
</template>
|
||||||
25
resources/js/Components/ui/avatar/index.ts
Normal file
25
resources/js/Components/ui/avatar/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as Avatar } from "./Avatar.vue"
|
||||||
|
export { default as AvatarFallback } from "./AvatarFallback.vue"
|
||||||
|
export { default as AvatarImage } from "./AvatarImage.vue"
|
||||||
|
|
||||||
|
export const avatarVariant = cva(
|
||||||
|
"inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: "h-10 w-10 text-xs",
|
||||||
|
base: "h-16 w-16 text-2xl",
|
||||||
|
lg: "h-32 w-32 text-5xl",
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
circle: "rounded-full",
|
||||||
|
square: "rounded-md",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AvatarVariants = VariantProps<typeof avatarVariant>
|
||||||
13
resources/js/Components/ui/breadcrumb/Breadcrumb.vue
Normal file
13
resources/js/Components/ui/breadcrumb/Breadcrumb.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav aria-label="breadcrumb" :class="props.class">
|
||||||
|
<slot />
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
22
resources/js/Components/ui/breadcrumb/BreadcrumbEllipsis.vue
Normal file
22
resources/js/Components/ui/breadcrumb/BreadcrumbEllipsis.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { MoreHorizontal } from "lucide-vue-next"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="cn('flex h-9 w-9 items-center justify-center', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<MoreHorizontal class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
<span class="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
16
resources/js/Components/ui/breadcrumb/BreadcrumbItem.vue
Normal file
16
resources/js/Components/ui/breadcrumb/BreadcrumbItem.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
:class="cn('inline-flex items-center gap-1.5', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
20
resources/js/Components/ui/breadcrumb/BreadcrumbLink.vue
Normal file
20
resources/js/Components/ui/breadcrumb/BreadcrumbLink.vue
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { Primitive } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>(), {
|
||||||
|
as: "a",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Primitive
|
||||||
|
:as="as"
|
||||||
|
:as-child="asChild"
|
||||||
|
:class="cn('transition-colors hover:text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
|
</template>
|
||||||
16
resources/js/Components/ui/breadcrumb/BreadcrumbList.vue
Normal file
16
resources/js/Components/ui/breadcrumb/BreadcrumbList.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ol
|
||||||
|
:class="cn('flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
19
resources/js/Components/ui/breadcrumb/BreadcrumbPage.vue
Normal file
19
resources/js/Components/ui/breadcrumb/BreadcrumbPage.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
:class="cn('font-normal text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { ChevronRight } from "lucide-vue-next"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
:class="cn('[&>svg]:size-3.5', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronRight />
|
||||||
|
</slot>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
7
resources/js/Components/ui/breadcrumb/index.ts
Normal file
7
resources/js/Components/ui/breadcrumb/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { default as Breadcrumb } from "./Breadcrumb.vue"
|
||||||
|
export { default as BreadcrumbEllipsis } from "./BreadcrumbEllipsis.vue"
|
||||||
|
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue"
|
||||||
|
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue"
|
||||||
|
export { default as BreadcrumbList } from "./BreadcrumbList.vue"
|
||||||
|
export { default as BreadcrumbPage } from "./BreadcrumbPage.vue"
|
||||||
|
export { default as BreadcrumbSeparator } from "./BreadcrumbSeparator.vue"
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { Primitive } from "reka-ui";
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
import { cn } from "@/lib/utils";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { buttonVariants } from ".";
|
import type { ButtonVariants } from "."
|
||||||
|
import { Primitive } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "."
|
||||||
|
|
||||||
const props = defineProps({
|
interface Props extends PrimitiveProps {
|
||||||
variant: { type: null, required: false },
|
variant?: ButtonVariants["variant"]
|
||||||
size: { type: null, required: false },
|
size?: ButtonVariants["size"]
|
||||||
class: { type: null, required: false },
|
class?: HTMLAttributes["class"]
|
||||||
asChild: { type: Boolean, required: false },
|
}
|
||||||
as: { type: null, required: false, default: "button" },
|
|
||||||
});
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
as: "button",
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
38
resources/js/Components/ui/button/index.ts
Normal file
38
resources/js/Components/ui/button/index.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as Button } from "./Button.vue"
|
||||||
|
|
||||||
|
export const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
"icon": "size-9",
|
||||||
|
"icon-sm": "size-8",
|
||||||
|
"icon-lg": "size-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||||
15
resources/js/Components/ui/collapsible/Collapsible.vue
Normal file
15
resources/js/Components/ui/collapsible/Collapsible.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleRootEmits, CollapsibleRootProps } from "reka-ui"
|
||||||
|
import { CollapsibleRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<CollapsibleRootProps>()
|
||||||
|
const emits = defineEmits<CollapsibleRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CollapsibleRoot v-slot="{ open }" v-bind="forwarded">
|
||||||
|
<slot :open="open" />
|
||||||
|
</CollapsibleRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleContentProps } from "reka-ui"
|
||||||
|
import { CollapsibleContent } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<CollapsibleContentProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CollapsibleContent v-bind="props" class="overflow-hidden transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
||||||
|
<slot />
|
||||||
|
</CollapsibleContent>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleTriggerProps } from "reka-ui"
|
||||||
|
import { CollapsibleTrigger } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<CollapsibleTriggerProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CollapsibleTrigger v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
</template>
|
||||||
3
resources/js/Components/ui/collapsible/index.js
Normal file
3
resources/js/Components/ui/collapsible/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as Collapsible } from "./Collapsible.vue";
|
||||||
|
export { default as CollapsibleContent } from "./CollapsibleContent.vue";
|
||||||
|
export { default as CollapsibleTrigger } from "./CollapsibleTrigger.vue";
|
||||||
3
resources/js/Components/ui/collapsible/index.ts
Normal file
3
resources/js/Components/ui/collapsible/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as Collapsible } from "./Collapsible.vue"
|
||||||
|
export { default as CollapsibleContent } from "./CollapsibleContent.vue"
|
||||||
|
export { default as CollapsibleTrigger } from "./CollapsibleTrigger.vue"
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui";
|
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from "reka-ui"
|
||||||
|
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuRootProps>()
|
||||||
defaultOpen: { type: Boolean, required: false },
|
const emits = defineEmits<DropdownMenuRootEmits>()
|
||||||
open: { type: Boolean, required: false },
|
|
||||||
dir: { type: String, required: false },
|
|
||||||
modal: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["update:open"]);
|
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(props, emits);
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,30 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"
|
||||||
import { Check } from "lucide-vue-next";
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Check } from "lucide-vue-next"
|
||||||
import {
|
import {
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuItemIndicator,
|
DropdownMenuItemIndicator,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui";
|
} from "reka-ui"
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>()
|
||||||
modelValue: { type: [Boolean, String], required: false },
|
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
textValue: { type: String, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["select", "update:modelValue"]);
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class=" cn(
|
||||||
cn(
|
|
||||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
<DropdownMenuItemIndicator>
|
<DropdownMenuItemIndicator>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,32 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
import {
|
import {
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui";
|
} from "reka-ui"
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(
|
||||||
forceMount: { type: Boolean, required: false },
|
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||||
loop: { type: Boolean, required: false },
|
{
|
||||||
side: { type: null, required: false },
|
sideOffset: 4,
|
||||||
sideOffset: { type: Number, required: false, default: 4 },
|
},
|
||||||
sideFlip: { type: Boolean, required: false },
|
)
|
||||||
align: { type: null, required: false },
|
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||||
alignOffset: { type: Number, required: false },
|
|
||||||
alignFlip: { type: Boolean, required: false },
|
|
||||||
avoidCollisions: { type: Boolean, required: false },
|
|
||||||
collisionBoundary: { type: null, required: false },
|
|
||||||
collisionPadding: { type: [Number, Object], required: false },
|
|
||||||
arrowPadding: { type: Number, required: false },
|
|
||||||
sticky: { type: String, required: false },
|
|
||||||
hideWhenDetached: { type: Boolean, required: false },
|
|
||||||
positionStrategy: { type: String, required: false },
|
|
||||||
updatePositionStrategy: { type: String, required: false },
|
|
||||||
disableUpdateOnLayoutShift: { type: Boolean, required: false },
|
|
||||||
prioritizePosition: { type: Boolean, required: false },
|
|
||||||
reference: { type: null, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits([
|
|
||||||
"escapeKeyDown",
|
|
||||||
"pointerDownOutside",
|
|
||||||
"focusOutside",
|
|
||||||
"interactOutside",
|
|
||||||
"closeAutoFocus",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||||
cn(
|
|
||||||
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
||||||
props.class,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { DropdownMenuGroup } from "reka-ui";
|
import type { DropdownMenuGroupProps } from "reka-ui"
|
||||||
|
import { DropdownMenuGroup } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuGroupProps>()
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,25 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuItemProps } from "reka-ui"
|
||||||
import { DropdownMenuItem, useForwardProps } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DropdownMenuItem, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
textValue: { type: String, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
inset: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps);
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="cn(
|
||||||
cn(
|
'relative flex cursor-default select-none items-center rounded-sm gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||||
'relative flex cursor-pointer select-none items-center rounded-sm gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
||||||
inset && 'pl-8',
|
inset && 'pl-8',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuLabelProps } from "reka-ui"
|
||||||
import { DropdownMenuLabel, useForwardProps } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DropdownMenuLabel, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
inset: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps);
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuLabel
|
<DropdownMenuLabel
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||||
cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { DropdownMenuRadioGroup, useForwardPropsEmits } from "reka-ui";
|
import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from "reka-ui"
|
||||||
|
import {
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuRadioGroupProps>()
|
||||||
modelValue: { type: String, required: false },
|
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["update:modelValue"]);
|
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(props, emits);
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,31 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"
|
||||||
import { Circle } from "lucide-vue-next";
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Circle } from "lucide-vue-next"
|
||||||
import {
|
import {
|
||||||
DropdownMenuItemIndicator,
|
DropdownMenuItemIndicator,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui";
|
} from "reka-ui"
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>()
|
||||||
value: { type: String, required: true },
|
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
textValue: { type: String, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const emits = defineEmits(["select"]);
|
const emits = defineEmits<DropdownMenuRadioItemEmits>()
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuRadioItem
|
<DropdownMenuRadioItem
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="cn(
|
||||||
cn(
|
|
||||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
<DropdownMenuItemIndicator>
|
<DropdownMenuItemIndicator>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuSeparatorProps } from "reka-ui"
|
||||||
import { DropdownMenuSeparator } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuSeparatorProps & {
|
||||||
asChild: { type: Boolean, required: false },
|
class?: HTMLAttributes["class"]
|
||||||
as: { type: null, required: false },
|
}>()
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuSeparator
|
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||||
v-bind="delegatedProps"
|
|
||||||
:class="cn('-mx-1 my-1 h-px bg-muted', props.class)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { cn } from "@/lib/utils";
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
class: { type: null, required: false },
|
class?: HTMLAttributes["class"]
|
||||||
});
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { DropdownMenuSub, useForwardPropsEmits } from "reka-ui";
|
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from "reka-ui"
|
||||||
|
import {
|
||||||
|
DropdownMenuSub,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuSubProps>()
|
||||||
defaultOpen: { type: Boolean, required: false },
|
const emits = defineEmits<DropdownMenuSubEmits>()
|
||||||
open: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["update:open"]);
|
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(props, emits);
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,25 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui"
|
||||||
import { DropdownMenuSubContent, useForwardPropsEmits } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()
|
||||||
forceMount: { type: Boolean, required: false },
|
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||||
loop: { type: Boolean, required: false },
|
|
||||||
sideOffset: { type: Number, required: false },
|
|
||||||
sideFlip: { type: Boolean, required: false },
|
|
||||||
alignOffset: { type: Number, required: false },
|
|
||||||
alignFlip: { type: Boolean, required: false },
|
|
||||||
avoidCollisions: { type: Boolean, required: false },
|
|
||||||
collisionBoundary: { type: null, required: false },
|
|
||||||
collisionPadding: { type: [Number, Object], required: false },
|
|
||||||
arrowPadding: { type: Number, required: false },
|
|
||||||
sticky: { type: String, required: false },
|
|
||||||
hideWhenDetached: { type: Boolean, required: false },
|
|
||||||
positionStrategy: { type: String, required: false },
|
|
||||||
updatePositionStrategy: { type: String, required: false },
|
|
||||||
disableUpdateOnLayoutShift: { type: Boolean, required: false },
|
|
||||||
prioritizePosition: { type: Boolean, required: false },
|
|
||||||
reference: { type: null, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits([
|
|
||||||
"escapeKeyDown",
|
|
||||||
"pointerDownOutside",
|
|
||||||
"focusOutside",
|
|
||||||
"interactOutside",
|
|
||||||
"entryFocus",
|
|
||||||
"openAutoFocus",
|
|
||||||
"closeAutoFocus",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuSubContent
|
<DropdownMenuSubContent
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="cn('z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', props.class)"
|
||||||
cn(
|
|
||||||
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
||||||
props.class,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuSubContent>
|
</DropdownMenuSubContent>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,28 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { DropdownMenuSubTriggerProps } from "reka-ui"
|
||||||
import { ChevronRight } from "lucide-vue-next";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { DropdownMenuSubTrigger, useForwardProps } from "reka-ui";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
import { cn } from "@/lib/utils";
|
import { ChevronRight } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
useForwardProps,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }>()
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
textValue: { type: String, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps);
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuSubTrigger
|
<DropdownMenuSubTrigger
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="cn(
|
||||||
cn(
|
|
||||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<ChevronRight class="ml-auto h-4 w-4" />
|
<ChevronRight class="ml-auto h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { DropdownMenuTrigger, useForwardProps } from "reka-ui";
|
import type { DropdownMenuTriggerProps } from "reka-ui"
|
||||||
|
import { DropdownMenuTrigger, useForwardProps } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<DropdownMenuTriggerProps>()
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(props);
|
const forwardedProps = useForwardProps(props)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuTrigger class="outline-none cursor-pointer" v-bind="forwardedProps">
|
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps">
|
||||||
<slot />
|
<slot />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
16
resources/js/Components/ui/dropdown-menu/index.ts
Normal file
16
resources/js/Components/ui/dropdown-menu/index.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
export { default as DropdownMenu } from "./DropdownMenu.vue"
|
||||||
|
|
||||||
|
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue"
|
||||||
|
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
|
||||||
|
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
|
||||||
|
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue"
|
||||||
|
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue"
|
||||||
|
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue"
|
||||||
|
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue"
|
||||||
|
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue"
|
||||||
|
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue"
|
||||||
|
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue"
|
||||||
|
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue"
|
||||||
|
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue"
|
||||||
|
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
|
||||||
|
export { DropdownMenuPortal } from "reka-ui"
|
||||||
|
|
@ -1,32 +1,33 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { useVModel } from "@vueuse/core";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { useVModel } from "@vueuse/core"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
defaultValue: { type: [String, Number], required: false },
|
defaultValue?: string | number
|
||||||
modelValue: { type: [String, Number], required: false },
|
modelValue?: string | number
|
||||||
class: { type: null, required: false },
|
class?: HTMLAttributes["class"]
|
||||||
});
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits(["update:modelValue"]);
|
const emits = defineEmits<{
|
||||||
|
(e: "update:modelValue", payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const modelValue = useVModel(props, "modelValue", emits, {
|
const modelValue = useVModel(props, "modelValue", emits, {
|
||||||
passive: true,
|
passive: true,
|
||||||
defaultValue: props.defaultValue,
|
defaultValue: props.defaultValue,
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<input
|
<input
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
:class="
|
:class="cn(
|
||||||
cn(
|
|
||||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)"
|
||||||
"
|
>
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
1
resources/js/Components/ui/input/index.ts
Normal file
1
resources/js/Components/ui/input/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Input } from "./Input.vue"
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronLeftIcon } from "lucide-vue-next";
|
import { ChevronLeftIcon } from "lucide-vue-next";
|
||||||
import { PaginationFirst, useForwardProps } from "reka-ui";
|
import { PaginationFirst, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
asChild: { type: Boolean, required: false },
|
asChild: { type: Boolean, required: false },
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { PaginationListItem } from "reka-ui";
|
import { PaginationListItem } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: { type: Number, required: true },
|
value: { type: Number, required: true },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronRightIcon } from "lucide-vue-next";
|
import { ChevronRightIcon } from "lucide-vue-next";
|
||||||
import { PaginationLast, useForwardProps } from "reka-ui";
|
import { PaginationLast, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
asChild: { type: Boolean, required: false },
|
asChild: { type: Boolean, required: false },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronRightIcon } from "lucide-vue-next";
|
import { ChevronRightIcon } from "lucide-vue-next";
|
||||||
import { PaginationNext, useForwardProps } from "reka-ui";
|
import { PaginationNext, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
asChild: { type: Boolean, required: false },
|
asChild: { type: Boolean, required: false },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronLeftIcon } from "lucide-vue-next";
|
import { ChevronLeftIcon } from "lucide-vue-next";
|
||||||
import { PaginationPrev, useForwardProps } from "reka-ui";
|
import { PaginationPrev, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
asChild: { type: Boolean, required: false },
|
asChild: { type: Boolean, required: false },
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { RangeCalendarCellTrigger, useForwardProps } from "reka-ui";
|
import { RangeCalendarCellTrigger, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
day: { type: null, required: true },
|
day: { type: null, required: true },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronRight } from "lucide-vue-next";
|
import { ChevronRight } from "lucide-vue-next";
|
||||||
import { RangeCalendarNext, useForwardProps } from "reka-ui";
|
import { RangeCalendarNext, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
nextPage: { type: Function, required: false },
|
nextPage: { type: Function, required: false },
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronLeft } from "lucide-vue-next";
|
import { ChevronLeft } from "lucide-vue-next";
|
||||||
import { RangeCalendarPrev, useForwardProps } from "reka-ui";
|
import { RangeCalendarPrev, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/Components/ui/button';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
prevPage: { type: Function, required: false },
|
prevPage: { type: Function, required: false },
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { SeparatorProps } from "reka-ui"
|
||||||
import { Separator } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Separator } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(defineProps<
|
||||||
orientation: { type: String, required: false, default: "horizontal" },
|
SeparatorProps & { class?: HTMLAttributes["class"] }
|
||||||
decorative: { type: Boolean, required: false, default: true },
|
>(), {
|
||||||
asChild: { type: Boolean, required: false },
|
orientation: "horizontal",
|
||||||
as: { type: null, required: false },
|
decorative: true,
|
||||||
class: { type: null, required: false },
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
1
resources/js/Components/ui/separator/index.ts
Normal file
1
resources/js/Components/ui/separator/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Separator } from "./Separator.vue"
|
||||||
19
resources/js/Components/ui/sheet/Sheet.vue
Normal file
19
resources/js/Components/ui/sheet/Sheet.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
|
||||||
|
import { DialogRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DialogRootProps>()
|
||||||
|
const emits = defineEmits<DialogRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogRoot
|
||||||
|
v-slot="slotProps"
|
||||||
|
data-slot="sheet"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
|
</DialogRoot>
|
||||||
|
</template>
|
||||||
15
resources/js/Components/ui/sheet/SheetClose.vue
Normal file
15
resources/js/Components/ui/sheet/SheetClose.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogCloseProps } from "reka-ui"
|
||||||
|
import { DialogClose } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DialogCloseProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogClose
|
||||||
|
data-slot="sheet-close"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DialogClose>
|
||||||
|
</template>
|
||||||
62
resources/js/Components/ui/sheet/SheetContent.vue
Normal file
62
resources/js/Components/ui/sheet/SheetContent.vue
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { X } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogPortal,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import SheetOverlay from "./SheetOverlay.vue"
|
||||||
|
|
||||||
|
interface SheetContentProps extends DialogContentProps {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
side?: "top" | "right" | "bottom" | "left"
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||||
|
side: "right",
|
||||||
|
})
|
||||||
|
const emits = defineEmits<DialogContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "side")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<DialogContent
|
||||||
|
data-slot="sheet-content"
|
||||||
|
:class="cn(
|
||||||
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||||
|
side === 'right'
|
||||||
|
&& 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
||||||
|
side === 'left'
|
||||||
|
&& 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
||||||
|
side === 'top'
|
||||||
|
&& 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
||||||
|
side === 'bottom'
|
||||||
|
&& 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
||||||
|
props.class)"
|
||||||
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<DialogClose
|
||||||
|
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
|
||||||
|
>
|
||||||
|
<X class="size-4" />
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</template>
|
||||||
21
resources/js/Components/ui/sheet/SheetDescription.vue
Normal file
21
resources/js/Components/ui/sheet/SheetDescription.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogDescriptionProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DialogDescription } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogDescription
|
||||||
|
data-slot="sheet-description"
|
||||||
|
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DialogDescription>
|
||||||
|
</template>
|
||||||
16
resources/js/Components/ui/sheet/SheetFooter.vue
Normal file
16
resources/js/Components/ui/sheet/SheetFooter.vue
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="sheet-footer"
|
||||||
|
:class="cn('mt-auto flex flex-col gap-2 p-4', props.class)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
15
resources/js/Components/ui/sheet/SheetHeader.vue
Normal file
15
resources/js/Components/ui/sheet/SheetHeader.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="sheet-header"
|
||||||
|
:class="cn('flex flex-col gap-1.5 p-4', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
21
resources/js/Components/ui/sheet/SheetOverlay.vue
Normal file
21
resources/js/Components/ui/sheet/SheetOverlay.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogOverlayProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DialogOverlay } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogOverlay
|
||||||
|
data-slot="sheet-overlay"
|
||||||
|
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DialogOverlay>
|
||||||
|
</template>
|
||||||
21
resources/js/Components/ui/sheet/SheetTitle.vue
Normal file
21
resources/js/Components/ui/sheet/SheetTitle.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogTitleProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DialogTitle } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogTitle
|
||||||
|
data-slot="sheet-title"
|
||||||
|
:class="cn('text-foreground font-semibold', props.class)"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DialogTitle>
|
||||||
|
</template>
|
||||||
15
resources/js/Components/ui/sheet/SheetTrigger.vue
Normal file
15
resources/js/Components/ui/sheet/SheetTrigger.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DialogTriggerProps } from "reka-ui"
|
||||||
|
import { DialogTrigger } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DialogTriggerProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogTrigger
|
||||||
|
data-slot="sheet-trigger"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DialogTrigger>
|
||||||
|
</template>
|
||||||
8
resources/js/Components/ui/sheet/index.js
Normal file
8
resources/js/Components/ui/sheet/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export { default as Sheet } from "./Sheet.vue";
|
||||||
|
export { default as SheetClose } from "./SheetClose.vue";
|
||||||
|
export { default as SheetContent } from "./SheetContent.vue";
|
||||||
|
export { default as SheetDescription } from "./SheetDescription.vue";
|
||||||
|
export { default as SheetFooter } from "./SheetFooter.vue";
|
||||||
|
export { default as SheetHeader } from "./SheetHeader.vue";
|
||||||
|
export { default as SheetTitle } from "./SheetTitle.vue";
|
||||||
|
export { default as SheetTrigger } from "./SheetTrigger.vue";
|
||||||
8
resources/js/Components/ui/sheet/index.ts
Normal file
8
resources/js/Components/ui/sheet/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export { default as Sheet } from "./Sheet.vue"
|
||||||
|
export { default as SheetClose } from "./SheetClose.vue"
|
||||||
|
export { default as SheetContent } from "./SheetContent.vue"
|
||||||
|
export { default as SheetDescription } from "./SheetDescription.vue"
|
||||||
|
export { default as SheetFooter } from "./SheetFooter.vue"
|
||||||
|
export { default as SheetHeader } from "./SheetHeader.vue"
|
||||||
|
export { default as SheetTitle } from "./SheetTitle.vue"
|
||||||
|
export { default as SheetTrigger } from "./SheetTrigger.vue"
|
||||||
17
resources/js/Components/ui/skeleton/Skeleton.vue
Normal file
17
resources/js/Components/ui/skeleton/Skeleton.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
interface SkeletonProps {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<SkeletonProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="skeleton"
|
||||||
|
:class="cn('animate-pulse rounded-md bg-primary/10', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
1
resources/js/Components/ui/skeleton/index.js
Normal file
1
resources/js/Components/ui/skeleton/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Skeleton } from "./Skeleton.vue";
|
||||||
1
resources/js/Components/ui/skeleton/index.ts
Normal file
1
resources/js/Components/ui/skeleton/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as Skeleton } from "./Skeleton.vue"
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { TooltipRoot, useForwardPropsEmits } from "reka-ui";
|
import type { TooltipRootEmits, TooltipRootProps } from "reka-ui"
|
||||||
|
import { TooltipRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<TooltipRootProps>()
|
||||||
defaultOpen: { type: Boolean, required: false },
|
const emits = defineEmits<TooltipRootEmits>()
|
||||||
open: { type: Boolean, required: false },
|
|
||||||
delayDuration: { type: Number, required: false },
|
|
||||||
disableHoverableContent: { type: Boolean, required: false },
|
|
||||||
disableClosingTrigger: { type: Boolean, required: false },
|
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
ignoreNonKeyboardFocus: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
const emits = defineEmits(["update:open"]);
|
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(props, emits);
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TooltipRoot v-bind="forwarded">
|
<TooltipRoot
|
||||||
<slot />
|
v-slot="slotProps"
|
||||||
|
data-slot="tooltip"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot v-bind="slotProps" />
|
||||||
</TooltipRoot>
|
</TooltipRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,34 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { reactiveOmit } from "@vueuse/core";
|
import type { TooltipContentEmits, TooltipContentProps } from "reka-ui"
|
||||||
import { TooltipContent, TooltipPortal, useForwardPropsEmits } from "reka-ui";
|
import type { HTMLAttributes } from "vue"
|
||||||
import { cn } from "@/lib/utils";
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { TooltipArrow, TooltipContent, TooltipPortal, useForwardPropsEmits } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes["class"] }>(), {
|
||||||
forceMount: { type: Boolean, required: false },
|
sideOffset: 4,
|
||||||
ariaLabel: { type: String, required: false },
|
})
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
side: { type: null, required: false },
|
|
||||||
sideOffset: { type: Number, required: false, default: 4 },
|
|
||||||
align: { type: null, required: false },
|
|
||||||
alignOffset: { type: Number, required: false },
|
|
||||||
avoidCollisions: { type: Boolean, required: false },
|
|
||||||
collisionBoundary: { type: null, required: false },
|
|
||||||
collisionPadding: { type: [Number, Object], required: false },
|
|
||||||
arrowPadding: { type: Number, required: false },
|
|
||||||
sticky: { type: String, required: false },
|
|
||||||
hideWhenDetached: { type: Boolean, required: false },
|
|
||||||
positionStrategy: { type: String, required: false },
|
|
||||||
updatePositionStrategy: { type: String, required: false },
|
|
||||||
class: { type: null, required: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
const emits = defineEmits(["escapeKeyDown", "pointerDownOutside"]);
|
const emits = defineEmits<TooltipContentEmits>()
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class");
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TooltipPortal>
|
<TooltipPortal>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
|
data-slot="tooltip-content"
|
||||||
v-bind="{ ...forwarded, ...$attrs }"
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
:class="
|
:class="cn('bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance', props.class)"
|
||||||
cn(
|
|
||||||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
||||||
props.class,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
<TooltipArrow class="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</TooltipPortal>
|
</TooltipPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { TooltipProvider } from "reka-ui";
|
import type { TooltipProviderProps } from "reka-ui"
|
||||||
|
import { TooltipProvider } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(defineProps<TooltipProviderProps>(), {
|
||||||
delayDuration: { type: Number, required: false },
|
delayDuration: 0,
|
||||||
skipDelayDuration: { type: Number, required: false },
|
})
|
||||||
disableHoverableContent: { type: Boolean, required: false },
|
|
||||||
disableClosingTrigger: { type: Boolean, required: false },
|
|
||||||
disabled: { type: Boolean, required: false },
|
|
||||||
ignoreNonKeyboardFocus: { type: Boolean, required: false },
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { TooltipTrigger } from "reka-ui";
|
import type { TooltipTriggerProps } from "reka-ui"
|
||||||
|
import { TooltipTrigger } from "reka-ui"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<TooltipTriggerProps>()
|
||||||
reference: { type: null, required: false },
|
|
||||||
asChild: { type: Boolean, required: false },
|
|
||||||
as: { type: null, required: false },
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TooltipTrigger v-bind="props">
|
<TooltipTrigger
|
||||||
|
data-slot="tooltip-trigger"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
4
resources/js/Components/ui/tooltip/index.ts
Normal file
4
resources/js/Components/ui/tooltip/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export { default as Tooltip } from "./Tooltip.vue"
|
||||||
|
export { default as TooltipContent } from "./TooltipContent.vue"
|
||||||
|
export { default as TooltipProvider } from "./TooltipProvider.vue"
|
||||||
|
export { default as TooltipTrigger } from "./TooltipTrigger.vue"
|
||||||
|
|
@ -43,9 +43,9 @@ const fmtDateDMY = (v) => {
|
||||||
<template #header> </template>
|
<template #header> </template>
|
||||||
<div class="py-12">
|
<div class="py-12">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<div class="px-3 bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||||
<div class="mx-auto max-w-4x1 py-3">
|
<div class="mx-auto max-w-4x1 py-3">
|
||||||
<div class="pb-3">
|
<div class="pb-3 px-3">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>Primeri</template>
|
<template #title>Primeri</template>
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ const submitAttachSegment = () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Card class="border-l-4 border-blue-400">
|
<Card class="border-l-4 border-blue-400 p-0">
|
||||||
<div class="p-3 flex justify-between items-center">
|
<div class="p-3 flex justify-between items-center">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
|
@ -271,8 +271,8 @@ const submitAttachSegment = () => {
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-1" :hidden="clientDetails">
|
<div class="pt-1" :hidden="clientDetails">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1 p-3">
|
<div class="p-3">
|
||||||
<PersonInfoGrid
|
<PersonInfoGrid
|
||||||
:types="types"
|
:types="types"
|
||||||
:person="client.person"
|
:person="client.person"
|
||||||
|
|
@ -285,8 +285,8 @@ const submitAttachSegment = () => {
|
||||||
<!-- Case details -->
|
<!-- Case details -->
|
||||||
<div class="pt-6">
|
<div class="pt-6">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card class="border-l-4 border-red-400">
|
<Card class="border-l-4 border-red-400 p-0">
|
||||||
<div class="mx-auto max-w-4x1 p-3 flex items-center justify-between">
|
<div class="p-3 flex items-center justify-between">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>{{ client_case.person.full_name }}</template>
|
<template #title>{{ client_case.person.full_name }}</template>
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
|
|
@ -309,8 +309,8 @@ const submitAttachSegment = () => {
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1 p-3">
|
<div class="p-3">
|
||||||
<PersonInfoGrid
|
<PersonInfoGrid
|
||||||
:types="types"
|
:types="types"
|
||||||
tab-color="red-600"
|
tab-color="red-600"
|
||||||
|
|
@ -326,8 +326,8 @@ const submitAttachSegment = () => {
|
||||||
<!-- Contracts section -->
|
<!-- Contracts section -->
|
||||||
<div class="pt-12">
|
<div class="pt-12">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1">
|
<div>
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title> Pogodbe </template>
|
<template #title> Pogodbe </template>
|
||||||
|
|
@ -369,9 +369,9 @@ const submitAttachSegment = () => {
|
||||||
<!-- Activities section -->
|
<!-- Activities section -->
|
||||||
<div class="pt-12">
|
<div class="pt-12">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1">
|
<div>
|
||||||
<div class="flex justify-between p-4">
|
<div class="flex justify-between p-3">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>Aktivnosti</template>
|
<template #title>Aktivnosti</template>
|
||||||
</SectionTitle>
|
</SectionTitle>
|
||||||
|
|
@ -410,8 +410,8 @@ const submitAttachSegment = () => {
|
||||||
<!-- Documents section -->
|
<!-- Documents section -->
|
||||||
<div class="pt-12">
|
<div class="pt-12">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1">
|
<div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>Dokumenti</template>
|
<template #title>Dokumenti</template>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ function formatDate(value) {
|
||||||
<!-- Header card (matches Client/Show header style) -->
|
<!-- Header card (matches Client/Show header style) -->
|
||||||
<div class="pt-6">
|
<div class="pt-6">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card class="border-l-4 border-blue-400">
|
<Card class="border-l-4 border-blue-400 p-0">
|
||||||
<div class="p-3 flex justify-between items-center">
|
<div class="p-3 flex justify-between items-center">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
|
@ -146,8 +146,8 @@ function formatDate(value) {
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<div class="mx-auto max-w-4x1 p-3">
|
<div class="p-3">
|
||||||
<PersonInfoGrid
|
<PersonInfoGrid
|
||||||
:types="types"
|
:types="types"
|
||||||
:person="client.person"
|
:person="client.person"
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,8 @@ const fmtCurrency = (v) => {
|
||||||
<template #header> </template>
|
<template #header> </template>
|
||||||
<div class="py-6">
|
<div class="py-6">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0">
|
||||||
<CardHeader class="p-5">
|
<CardHeader>
|
||||||
<CardTitle>Naročniki</CardTitle>
|
<CardTitle>Naročniki</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="p-0">
|
<CardContent class="p-0">
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ function applySearch() {
|
||||||
<template #header></template>
|
<template #header></template>
|
||||||
<div class="pt-6">
|
<div class="pt-6">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card class="border-l-4 border-blue-400">
|
<Card class="border-l-4 border-blue-400 p-0!">
|
||||||
<div class="p-3 flex justify-between items-center">
|
<div class="p-3 flex justify-between items-center">
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
<template #title>
|
<template #title>
|
||||||
|
|
@ -77,8 +77,8 @@ function applySearch() {
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-1">
|
<div class="pt-1">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<Card>
|
<Card class="p-0!">
|
||||||
<div class="mx-auto max-w-4x1 p-3">
|
<div class="p-3">
|
||||||
<PersonInfoGrid
|
<PersonInfoGrid
|
||||||
:types="types"
|
:types="types"
|
||||||
:person="client.person"
|
:person="client.person"
|
||||||
|
|
|
||||||
|
|
@ -1,620 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
|
||||||
import { computed, ref, onMounted } from "vue";
|
|
||||||
import { usePage, Link } from "@inertiajs/vue3";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
||||||
import {
|
|
||||||
faUsers,
|
|
||||||
faUserPlus,
|
|
||||||
faClipboardList,
|
|
||||||
faFileLines,
|
|
||||||
faCloudArrowUp,
|
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { faFileContract } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
kpis: Object,
|
|
||||||
activities: Array,
|
|
||||||
trends: Object,
|
|
||||||
systemHealth: Object,
|
|
||||||
staleCases: Array,
|
|
||||||
fieldJobsAssignedToday: Array,
|
|
||||||
importsInProgress: Array,
|
|
||||||
activeTemplates: Array,
|
|
||||||
smsStats: Array,
|
|
||||||
});
|
|
||||||
|
|
||||||
const kpiDefs = [
|
|
||||||
{ key: "clients_total", label: "Vse stranke", icon: faUsers, route: "client" },
|
|
||||||
{ key: "clients_new_7d", label: "Nove (7d)", icon: faUserPlus, route: "client" },
|
|
||||||
{
|
|
||||||
key: "field_jobs_today",
|
|
||||||
label: "Terenske danes",
|
|
||||||
icon: faClipboardList,
|
|
||||||
route: "fieldjobs.index",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "documents_today",
|
|
||||||
label: "Dokumenti danes",
|
|
||||||
icon: faFileLines,
|
|
||||||
route: "clientCase",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "active_imports",
|
|
||||||
label: "Aktivni uvozi",
|
|
||||||
icon: faCloudArrowUp,
|
|
||||||
route: "imports.index",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "active_contracts",
|
|
||||||
label: "Aktivne pogodbe",
|
|
||||||
icon: faFileContract,
|
|
||||||
route: "clientCase",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const page = usePage();
|
|
||||||
|
|
||||||
// Simple sparkline path generator
|
|
||||||
function sparkline(values) {
|
|
||||||
if (!values || !values.length) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const max = Math.max(...values) || 1;
|
|
||||||
const h = 24;
|
|
||||||
const w = 60;
|
|
||||||
const step = w / (values.length - 1 || 1);
|
|
||||||
return values
|
|
||||||
.map(
|
|
||||||
(v, i) =>
|
|
||||||
`${i === 0 ? "M" : "L"}${(i * step).toFixed(2)},${(h - (v / max) * h).toFixed(2)}`
|
|
||||||
)
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove single relatedTarget helper and replace with multi-link builder
|
|
||||||
function buildRelated(a) {
|
|
||||||
const links = [];
|
|
||||||
// Only client case link (other routes not defined yet)
|
|
||||||
if (a.client_case_uuid || a.client_case_id) {
|
|
||||||
const caseParam = a.client_case_uuid || a.client_case_id;
|
|
||||||
try {
|
|
||||||
// Prefer Ziggy when available and force stringification here
|
|
||||||
const href = String(route("clientCase.show", { client_case: caseParam }));
|
|
||||||
links.push({
|
|
||||||
type: "client_case",
|
|
||||||
label: "Primer",
|
|
||||||
href,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// Safe fallback to a best-effort URL to avoid breaking render
|
|
||||||
links.push({
|
|
||||||
type: "client_case",
|
|
||||||
label: "Primer",
|
|
||||||
href: `/client-cases/${caseParam}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activityItems = computed(() =>
|
|
||||||
(props.activities || []).map((a) => ({ ...a, links: buildRelated(a) }))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Format stale days label: never negative; '<1 dan' if 0<=value<1; else integer with proper suffix.
|
|
||||||
function formatStaleDaysLabel(value) {
|
|
||||||
const num = Number.parseFloat(value);
|
|
||||||
if (Number.isNaN(num)) {
|
|
||||||
return "—";
|
|
||||||
}
|
|
||||||
if (num < 1) {
|
|
||||||
return "<1 dan";
|
|
||||||
}
|
|
||||||
const whole = Math.floor(num);
|
|
||||||
return whole === 1 ? "1 dan" : whole + " dni";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Robust time formatter to avoid fixed 02:00:00 (timezone / fallback issues)
|
|
||||||
function formatJobTime(ts) {
|
|
||||||
if (!ts) return "";
|
|
||||||
try {
|
|
||||||
const d = new Date(ts);
|
|
||||||
if (isNaN(d.getTime())) return "";
|
|
||||||
// Show HH:MM (24h) and seconds only if non-zero seconds
|
|
||||||
const pad = (n) => n.toString().padStart(2, "0");
|
|
||||||
const h = pad(d.getHours());
|
|
||||||
const m = pad(d.getMinutes());
|
|
||||||
const s = d.getSeconds();
|
|
||||||
return s ? `${h}:${m}:${pad(s)}` : `${h}:${m}`;
|
|
||||||
} catch (e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safely build a client case href using Ziggy when available, with a plain fallback.
|
|
||||||
function safeCaseHref(uuid, segment = null) {
|
|
||||||
if (!uuid) {
|
|
||||||
return "#";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const params = { client_case: uuid };
|
|
||||||
if (segment != null) {
|
|
||||||
params.segment = segment;
|
|
||||||
}
|
|
||||||
return String(route("clientCase.show", params));
|
|
||||||
} catch (e) {
|
|
||||||
return segment != null
|
|
||||||
? `/client-cases/${uuid}?segment=${segment}`
|
|
||||||
: `/client-cases/${uuid}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<AppLayout title="Nadzorna plošča">
|
|
||||||
<template #header> </template>
|
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto space-y-10 py-6">
|
|
||||||
<!-- KPI Cards with trends -->
|
|
||||||
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
|
||||||
<Link
|
|
||||||
v-for="k in kpiDefs"
|
|
||||||
:key="k.key"
|
|
||||||
:href="route(k.route)"
|
|
||||||
class="group relative bg-white border rounded-xl shadow-sm px-4 py-5 flex flex-col gap-3 hover:border-indigo-300 hover:shadow transition focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span
|
|
||||||
class="inline-flex items-center justify-center h-10 w-10 rounded-md bg-indigo-50 text-indigo-600 group-hover:bg-indigo-100"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon :icon="k.icon" class="w-5 h-5" />
|
|
||||||
</span>
|
|
||||||
<span class="text-[11px] text-gray-400 uppercase tracking-wide">{{
|
|
||||||
k.label
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-end gap-2">
|
|
||||||
<span class="text-2xl font-semibold tracking-tight text-gray-900">{{
|
|
||||||
props.kpis?.[k.key] ?? "—"
|
|
||||||
}}</span>
|
|
||||||
<span
|
|
||||||
class="text-[10px] text-indigo-500 opacity-0 group-hover:opacity-100 transition"
|
|
||||||
>Odpri →</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div v-if="trends" class="mt-1 h-6">
|
|
||||||
<svg
|
|
||||||
v-if="k.key === 'clients_new_7d'"
|
|
||||||
:viewBox="'0 0 60 24'"
|
|
||||||
class="w-full h-6 overflow-visible"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
:d="sparkline(trends.clients_new)"
|
|
||||||
fill="none"
|
|
||||||
class="stroke-indigo-400"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
v-else-if="k.key === 'documents_today'"
|
|
||||||
:viewBox="'0 0 60 24'"
|
|
||||||
class="w-full h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
:d="sparkline(trends.documents_new)"
|
|
||||||
fill="none"
|
|
||||||
class="stroke-emerald-400"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
v-else-if="k.key === 'field_jobs_today'"
|
|
||||||
:viewBox="'0 0 60 24'"
|
|
||||||
class="w-full h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
:d="sparkline(trends.field_jobs)"
|
|
||||||
fill="none"
|
|
||||||
class="stroke-amber-400"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
v-else-if="k.key === 'active_imports'"
|
|
||||||
:viewBox="'0 0 60 24'"
|
|
||||||
class="w-full h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
:d="sparkline(trends.imports_new)"
|
|
||||||
fill="none"
|
|
||||||
class="stroke-fuchsia-400"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid lg:grid-cols-3 gap-8">
|
|
||||||
<!-- Activity Feed -->
|
|
||||||
<div class="lg:col-span-1 space-y-4">
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-5 flex flex-col gap-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase">
|
|
||||||
Aktivnost
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<ul class="divide-y divide-gray-100 text-sm" v-if="activities">
|
|
||||||
<li
|
|
||||||
v-for="a in activityItems"
|
|
||||||
:key="a.id"
|
|
||||||
class="py-2 flex items-start gap-3"
|
|
||||||
>
|
|
||||||
<span class="w-2 h-2 mt-2 rounded-full bg-indigo-400" />
|
|
||||||
<div class="flex-1 min-w-0 space-y-1">
|
|
||||||
<p class="text-gray-700 line-clamp-2">
|
|
||||||
{{ a.note || "Dogodek" }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
|
||||||
<span class="text-[11px] text-gray-400">{{
|
|
||||||
new Date(a.created_at).toLocaleString()
|
|
||||||
}}</span>
|
|
||||||
<Link
|
|
||||||
v-for="l in a.links"
|
|
||||||
:key="l.type + l.href"
|
|
||||||
:href="l.href"
|
|
||||||
class="text-[10px] px-2 py-0.5 rounded-full bg-indigo-50 text-indigo-600 hover:bg-indigo-100 font-medium tracking-wide"
|
|
||||||
>{{ l.label }}</Link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="!activities?.length"
|
|
||||||
class="py-4 text-xs text-gray-500 text-center"
|
|
||||||
>
|
|
||||||
Ni zabeleženih aktivnosti.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-else class="animate-pulse space-y-2">
|
|
||||||
<li v-for="n in 5" :key="n" class="h-5 bg-gray-100 rounded" />
|
|
||||||
</ul>
|
|
||||||
<div class="pt-1 flex justify-between items-center text-[11px]">
|
|
||||||
<Link
|
|
||||||
:href="route('dashboard')"
|
|
||||||
class="inline-flex items-center gap-1 font-medium text-indigo-600 hover:underline"
|
|
||||||
>Več kmalu
|
|
||||||
<FontAwesomeIcon :icon="faArrowUpRightFromSquare" class="w-3 h-3"
|
|
||||||
/></Link>
|
|
||||||
<span v-if="systemHealth" class="text-gray-400"
|
|
||||||
>Posodobljeno
|
|
||||||
{{ new Date(systemHealth.generated_at).toLocaleTimeString() }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right side panels -->
|
|
||||||
<div class="lg:col-span-2 space-y-8">
|
|
||||||
<!-- SMS Overview -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
SMS stanje
|
|
||||||
</h3>
|
|
||||||
<div v-if="props.smsStats?.length" class="overflow-x-auto">
|
|
||||||
<table class="w-full text-sm">
|
|
||||||
<thead class="bg-gray-50 text-gray-600 text-xs uppercase tracking-wider">
|
|
||||||
<tr>
|
|
||||||
<th class="px-3 py-2 text-left">Profil</th>
|
|
||||||
<th class="px-3 py-2 text-left">Bilanca</th>
|
|
||||||
<th class="px-3 py-2 text-left">Danes (skupaj)</th>
|
|
||||||
<th class="px-3 py-2 text-left">Sent</th>
|
|
||||||
<th class="px-3 py-2 text-left">Delivered</th>
|
|
||||||
<th class="px-3 py-2 text-left">Failed</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="p in props.smsStats"
|
|
||||||
:key="p.id"
|
|
||||||
class="border-t last:border-b"
|
|
||||||
>
|
|
||||||
<td class="px-3 py-2">
|
|
||||||
<span class="font-medium text-gray-900">{{ p.name }}</span>
|
|
||||||
<span
|
|
||||||
class="ml-2 text-[11px]"
|
|
||||||
:class="p.active ? 'text-emerald-600' : 'text-gray-400'"
|
|
||||||
>{{ p.active ? "Aktiven" : "Neaktiven" }}</span
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td class="px-3 py-2 text-gray-700">
|
|
||||||
{{ p.balance ?? "—" }}
|
|
||||||
</td>
|
|
||||||
<td class="px-3 py-2">{{ p.today?.total ?? 0 }}</td>
|
|
||||||
<td class="px-3 py-2 text-sky-700">{{ p.today?.sent ?? 0 }}</td>
|
|
||||||
<td class="px-3 py-2 text-emerald-700">
|
|
||||||
{{ p.today?.delivered ?? 0 }}
|
|
||||||
</td>
|
|
||||||
<td class="px-3 py-2 text-rose-700">{{ p.today?.failed ?? 0 }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div v-else class="text-sm text-gray-500">Ni podatkov o SMS.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- System Health -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
System Health
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
v-if="systemHealth"
|
|
||||||
class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4 text-sm"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<span class="text-[11px] uppercase text-gray-400">Queue backlog</span>
|
|
||||||
<span class="font-semibold text-gray-800">{{
|
|
||||||
systemHealth.queue_backlog ?? "—"
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<span class="text-[11px] uppercase text-gray-400">Failed jobs</span>
|
|
||||||
<span class="font-semibold text-gray-800">{{
|
|
||||||
systemHealth.failed_jobs ?? "—"
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<span class="text-[11px] uppercase text-gray-400"
|
|
||||||
>Last activity (min)</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="font-semibold text-gray-800"
|
|
||||||
:title="
|
|
||||||
systemHealth.last_activity_iso
|
|
||||||
? new Date(systemHealth.last_activity_iso).toLocaleString()
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>{{
|
|
||||||
Math.max(0, parseInt(systemHealth.last_activity_minutes ?? 0))
|
|
||||||
}}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<span class="text-[11px] uppercase text-gray-400">Generated</span>
|
|
||||||
<span class="font-semibold text-gray-800">{{
|
|
||||||
new Date(systemHealth.generated_at).toLocaleTimeString()
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="grid sm:grid-cols-4 gap-4 animate-pulse">
|
|
||||||
<div v-for="n in 4" :key="n" class="h-10 bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Completed Field Jobs Trend (7 dni) -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
Zaključena terenska dela (7 dni)
|
|
||||||
</h3>
|
|
||||||
<div v-if="trends" class="h-24">
|
|
||||||
<svg viewBox="0 0 140 60" class="w-full h-full">
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="fjc" x1="0" x2="0" y1="0" y2="1">
|
|
||||||
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.35" />
|
|
||||||
<stop offset="100%" stop-color="#6366f1" stop-opacity="0" />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path
|
|
||||||
v-if="trends.field_jobs_completed"
|
|
||||||
:d="sparkline(trends.field_jobs_completed)"
|
|
||||||
stroke="#6366f1"
|
|
||||||
stroke-width="2"
|
|
||||||
fill="none"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-linecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<div class="mt-2 flex gap-2 text-[10px] text-gray-400">
|
|
||||||
<span
|
|
||||||
v-for="(l, i) in trends.labels"
|
|
||||||
:key="i"
|
|
||||||
class="flex-1 truncate text-center"
|
|
||||||
>{{ l.slice(5) }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="h-24 animate-pulse bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stale Cases -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
Stari primeri brez aktivnosti
|
|
||||||
</h3>
|
|
||||||
<ul v-if="staleCases" class="divide-y divide-gray-100 text-sm">
|
|
||||||
<li
|
|
||||||
v-for="c in staleCases"
|
|
||||||
:key="c.id"
|
|
||||||
class="py-2 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<Link
|
|
||||||
v-if="c?.uuid"
|
|
||||||
:href="safeCaseHref(c.uuid)"
|
|
||||||
class="text-indigo-600 hover:underline font-medium"
|
|
||||||
>{{ c.client_ref || c.uuid.slice(0, 8) }}</Link
|
|
||||||
>
|
|
||||||
<span v-else class="text-gray-700 font-medium">{{
|
|
||||||
c.client_ref || "Primer"
|
|
||||||
}}</span>
|
|
||||||
<p class="text-[11px] text-gray-400">
|
|
||||||
Brez aktivnosti:
|
|
||||||
{{ formatStaleDaysLabel(c.days_without_activity ?? c.days_stale) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span class="text-[10px] px-2 py-0.5 rounded bg-amber-50 text-amber-600"
|
|
||||||
>Stale</span
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="!staleCases.length"
|
|
||||||
class="py-4 text-xs text-gray-500 text-center"
|
|
||||||
>
|
|
||||||
Ni starih primerov.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-else class="space-y-2 animate-pulse">
|
|
||||||
<div v-for="n in 5" :key="n" class="h-5 bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Field Jobs Assigned Today -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
Današnje dodelitve terenskih
|
|
||||||
</h3>
|
|
||||||
<ul v-if="fieldJobsAssignedToday" class="divide-y divide-gray-100 text-sm">
|
|
||||||
<li
|
|
||||||
v-for="f in fieldJobsAssignedToday"
|
|
||||||
:key="f.id"
|
|
||||||
class="py-2 flex items-start justify-between gap-3"
|
|
||||||
>
|
|
||||||
<div class="min-w-0 flex-1">
|
|
||||||
<p class="text-gray-700 text-sm font-medium">
|
|
||||||
#{{ f.id }}
|
|
||||||
<template v-if="f.contract">
|
|
||||||
·
|
|
||||||
<Link
|
|
||||||
v-if="f.contract.client_case_uuid"
|
|
||||||
:href="
|
|
||||||
safeCaseHref(f.contract.client_case_uuid, f.contract.segment_id)
|
|
||||||
"
|
|
||||||
class="text-indigo-600 hover:underline"
|
|
||||||
>
|
|
||||||
{{ f.contract.reference || f.contract.uuid?.slice(0, 8) }}
|
|
||||||
</Link>
|
|
||||||
<span v-else class="text-gray-700">{{
|
|
||||||
f.contract.reference || f.contract.uuid?.slice(0, 8)
|
|
||||||
}}</span>
|
|
||||||
<span v-if="f.contract.person_full_name" class="text-gray-500">
|
|
||||||
– {{ f.contract.person_full_name }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<p class="text-[11px] text-gray-400">
|
|
||||||
{{ formatJobTime(f.created_at) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span
|
|
||||||
v-if="f.priority"
|
|
||||||
class="text-[10px] px-2 py-0.5 rounded bg-rose-50 text-rose-600"
|
|
||||||
>Prioriteta</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="!fieldJobsAssignedToday.length"
|
|
||||||
class="py-4 text-xs text-gray-500 text-center"
|
|
||||||
>
|
|
||||||
Ni dodelitev.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-else class="space-y-2 animate-pulse">
|
|
||||||
<div v-for="n in 5" :key="n" class="h-5 bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Imports In Progress -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
Uvozi v teku
|
|
||||||
</h3>
|
|
||||||
<ul v-if="importsInProgress" class="divide-y divide-gray-100 text-sm">
|
|
||||||
<li v-for="im in importsInProgress" :key="im.id" class="py-2 space-y-1">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p class="font-medium text-gray-700 truncate">
|
|
||||||
{{ im.file_name }}
|
|
||||||
</p>
|
|
||||||
<span
|
|
||||||
class="text-[10px] px-2 py-0.5 rounded-full bg-indigo-50 text-indigo-600"
|
|
||||||
>{{ im.status }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-2 bg-gray-100 rounded overflow-hidden">
|
|
||||||
<div
|
|
||||||
class="h-full bg-indigo-500"
|
|
||||||
:style="{ width: (im.progress_pct || 0) + '%' }"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<p class="text-[10px] text-gray-400">
|
|
||||||
{{ im.imported_rows }}/{{ im.total_rows }} (veljavnih:
|
|
||||||
{{ im.valid_rows }}, neveljavnih: {{ im.invalid_rows }})
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="!importsInProgress.length"
|
|
||||||
class="py-4 text-xs text-gray-500 text-center"
|
|
||||||
>
|
|
||||||
Ni aktivnih uvozov.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-else class="space-y-2 animate-pulse">
|
|
||||||
<div v-for="n in 4" :key="n" class="h-5 bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Active Document Templates -->
|
|
||||||
<div class="bg-white border rounded-xl shadow-sm p-6">
|
|
||||||
<h3 class="text-sm font-semibold tracking-wide text-gray-700 uppercase mb-4">
|
|
||||||
Aktivne predloge dokumentov
|
|
||||||
</h3>
|
|
||||||
<ul v-if="activeTemplates" class="divide-y divide-gray-100 text-sm">
|
|
||||||
<li
|
|
||||||
v-for="t in activeTemplates"
|
|
||||||
:key="t.id"
|
|
||||||
class="py-2 flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="text-gray-700 font-medium truncate">
|
|
||||||
{{ t.name }}
|
|
||||||
</p>
|
|
||||||
<p class="text-[11px] text-gray-400">
|
|
||||||
v{{ t.version }} · {{ new Date(t.updated_at).toLocaleDateString() }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Link
|
|
||||||
:href="route('admin.document-templates.edit', t.id)"
|
|
||||||
class="text-[10px] px-2 py-0.5 rounded bg-indigo-50 text-indigo-600 hover:bg-indigo-100"
|
|
||||||
>Uredi</Link
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
v-if="!activeTemplates.length"
|
|
||||||
class="py-4 text-xs text-gray-500 text-center"
|
|
||||||
>
|
|
||||||
Ni aktivnih predlog.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div v-else class="space-y-2 animate-pulse">
|
|
||||||
<div v-for="n in 5" :key="n" class="h-5 bg-gray-100 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ...end of right side panels -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AppLayout>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
|
||||||
import SimpleKpiCard from "./Partials/SimpleKpiCard.vue";
|
import SimpleKpiCard from "./Partials/SimpleKpiCard.vue";
|
||||||
import ActivityFeed from "./Partials/ActivityFeed.vue";
|
import ActivityFeed from "./Partials/ActivityFeed.vue";
|
||||||
import SmsOverview from "./Partials/SmsOverview.vue";
|
import SmsOverview from "./Partials/SmsOverview.vue";
|
||||||
import CompletedFieldJobsTrend from "./Partials/CompletedFieldJobsTrend.vue";
|
import CompletedFieldJobsTrend from "./Partials/CompletedFieldJobsTrend.vue";
|
||||||
import FieldJobsAssignedToday from "./Partials/FieldJobsAssignedToday.vue";
|
import FieldJobsAssignedToday from "./Partials/FieldJobsAssignedToday.vue";
|
||||||
import { Users, FileText, Banknote, CalendarCheck } from "lucide-vue-next";
|
import { Users, FileText, Banknote, CalendarCheck } from "lucide-vue-next";
|
||||||
|
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
kpis: Object,
|
kpis: Object,
|
||||||
|
|
@ -41,6 +41,8 @@ const formatBalance = (amount) => {
|
||||||
label="Aktivni stranke"
|
label="Aktivni stranke"
|
||||||
:value="kpis?.active_clients"
|
:value="kpis?.active_clients"
|
||||||
:icon="Users"
|
:icon="Users"
|
||||||
|
icon-bg="bg-chart-2/10"
|
||||||
|
icon-color="text-chart-2"
|
||||||
/>
|
/>
|
||||||
<SimpleKpiCard
|
<SimpleKpiCard
|
||||||
label="Aktivne pogodbe"
|
label="Aktivne pogodbe"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import laravel from 'laravel-vite-plugin';
|
import laravel from 'laravel-vite-plugin';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
@ -20,5 +21,9 @@ export default defineConfig({
|
||||||
css: {
|
css: {
|
||||||
postcss: './postcss.config.js',
|
postcss: './postcss.config.js',
|
||||||
},
|
},
|
||||||
// Default resolution
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './resources/js'),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user