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",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
"build": "vite build",
|
||||
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inertiajs/vue3": "2.0",
|
||||
|
|
@ -11,14 +12,17 @@
|
|||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/postcss": "^4.1.16",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"axios": "^1.7.4",
|
||||
"laravel-vite-plugin": "^2.0.1",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.7",
|
||||
"vue": "^3.3.13"
|
||||
"vue": "^3.3.13",
|
||||
"vue-tsc": "^3.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, useAttrs } from "vue";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Calendar } from "@/Components/ui/calendar";
|
||||
import {
|
||||
|
|
@ -13,6 +13,8 @@ import { format } from "date-fns";
|
|||
import { sl } from "date-fns/locale";
|
||||
import { CalendarDate, parseDate } from "@internationalized/date";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Date, String, null],
|
||||
|
|
@ -42,6 +44,13 @@ const props = defineProps({
|
|||
|
||||
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
|
||||
const toCalendarDate = (value) => {
|
||||
if (!value) return null;
|
||||
|
|
@ -115,13 +124,15 @@ const open = ref(false);
|
|||
<Popover v-model:open="open">
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
:id="id"
|
||||
v-bind="forwardedAttrs"
|
||||
:id="controlId"
|
||||
variant="outline"
|
||||
:class="
|
||||
cn(
|
||||
'w-full justify-start text-left font-normal',
|
||||
!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"
|
||||
|
|
|
|||
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>
|
||||
<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 flex-wrap gap-2">
|
||||
<span
|
||||
|
|
@ -61,7 +61,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
|||
</DropdownMenu>
|
||||
</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.address}, ${address.post_code} ${address.city}`
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
|||
<template>
|
||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<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 flex-wrap gap-2">
|
||||
<span
|
||||
|
|
@ -68,6 +68,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
|||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-1">
|
||||
<p class="text-sm font-medium text-gray-900 leading-relaxed">
|
||||
{{ email?.value || email?.email || email?.address || "-" }}
|
||||
</p>
|
||||
|
|
@ -77,6 +78,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
|||
>
|
||||
{{ email.note }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const handleSms = (phone) => emit("sms", phone);
|
|||
<template>
|
||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<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 flex-wrap gap-2">
|
||||
<span
|
||||
|
|
@ -79,7 +79,9 @@ const handleSms = (phone) => emit("sms", phone);
|
|||
</DropdownMenu>
|
||||
</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>
|
||||
</template>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ import { router, usePage } from "@inertiajs/vue3";
|
|||
import { useForm, Field as FormField } from "vee-validate";
|
||||
import { toTypedSchema } from "@vee-validate/zod";
|
||||
import * as z from "zod";
|
||||
import {
|
||||
FormControl,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/Components/ui/form";
|
||||
import { FormControl, FormItem, FormLabel, FormMessage } from "@/Components/ui/form";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Textarea } from "@/Components/ui/textarea";
|
||||
import {
|
||||
|
|
@ -158,8 +153,7 @@ const formSchema = toTypedSchema(
|
|||
(val) => {
|
||||
const encoding = isGsm7(val) ? "GSM-7" : "UCS-2";
|
||||
const maxAllowed = encoding === "GSM-7" ? 640 : 320;
|
||||
const count =
|
||||
encoding === "GSM-7" ? gsm7Length(val) : ucs2Length(val);
|
||||
const count = encoding === "GSM-7" ? gsm7Length(val) : ucs2Length(val);
|
||||
return count <= maxAllowed;
|
||||
},
|
||||
{
|
||||
|
|
@ -196,9 +190,7 @@ const sendersForSelectedProfile = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const smsEncoding = computed(() =>
|
||||
isGsm7(form.values.message) ? "GSM-7" : "UCS-2"
|
||||
);
|
||||
const smsEncoding = computed(() => (isGsm7(form.values.message) ? "GSM-7" : "UCS-2"));
|
||||
|
||||
const charCount = computed(() =>
|
||||
smsEncoding.value === "GSM-7"
|
||||
|
|
@ -222,13 +214,9 @@ const segments = computed(() => {
|
|||
|
||||
const creditsNeeded = computed(() => segments.value);
|
||||
|
||||
const maxAllowed = computed(() =>
|
||||
smsEncoding.value === "GSM-7" ? 640 : 320
|
||||
);
|
||||
const maxAllowed = computed(() => (smsEncoding.value === "GSM-7" ? 640 : 320));
|
||||
|
||||
const remaining = computed(() =>
|
||||
Math.max(0, maxAllowed.value - charCount.value)
|
||||
);
|
||||
const remaining = computed(() => Math.max(0, maxAllowed.value - charCount.value));
|
||||
|
||||
// Truncate message if exceeds limit
|
||||
watch(
|
||||
|
|
@ -236,10 +224,7 @@ watch(
|
|||
(val) => {
|
||||
const limit = maxAllowed.value;
|
||||
if (charCount.value > limit) {
|
||||
form.setFieldValue(
|
||||
"message",
|
||||
truncateToLimit(val, limit, smsEncoding.value)
|
||||
);
|
||||
form.setFieldValue("message", truncateToLimit(val, limit, smsEncoding.value));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -299,14 +284,10 @@ const buildVarsFromSelectedContract = () => {
|
|||
type: c.account.type,
|
||||
initial_amount:
|
||||
c.account.initial_amount ??
|
||||
(c.account.initial_amount_raw
|
||||
? formatEu(c.account.initial_amount_raw)
|
||||
: null),
|
||||
(c.account.initial_amount_raw ? formatEu(c.account.initial_amount_raw) : null),
|
||||
balance_amount:
|
||||
c.account.balance_amount ??
|
||||
(c.account.balance_amount_raw
|
||||
? formatEu(c.account.balance_amount_raw)
|
||||
: null),
|
||||
(c.account.balance_amount_raw ? formatEu(c.account.balance_amount_raw) : null),
|
||||
initial_amount_raw: c.account.initial_amount_raw ?? null,
|
||||
balance_amount_raw: c.account.balance_amount_raw ?? null,
|
||||
};
|
||||
|
|
@ -497,11 +478,7 @@ const open = computed({
|
|||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">—</SelectItem>
|
||||
<SelectItem
|
||||
v-for="p in pageSmsProfiles"
|
||||
:key="p.id"
|
||||
:value="p.id"
|
||||
>
|
||||
<SelectItem v-for="p in pageSmsProfiles" :key="p.id" :value="p.id">
|
||||
{{ p.name || "Profil #" + p.id }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
|
@ -546,11 +523,7 @@ const open = computed({
|
|||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">—</SelectItem>
|
||||
<SelectItem
|
||||
v-for="c in contractsForCase"
|
||||
:key="c.uuid"
|
||||
:value="c.uuid"
|
||||
>
|
||||
<SelectItem v-for="c in contractsForCase" :key="c.uuid" :value="c.uuid">
|
||||
{{ c.reference || c.uuid }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
|
@ -574,11 +547,7 @@ const open = computed({
|
|||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem :value="null">—</SelectItem>
|
||||
<SelectItem
|
||||
v-for="t in pageSmsTemplates"
|
||||
:key="t.id"
|
||||
:value="t.id"
|
||||
>
|
||||
<SelectItem v-for="t in pageSmsTemplates" :key="t.id" :value="t.id">
|
||||
{{ t.name || "Predloga #" + t.id }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
|
@ -626,14 +595,13 @@ const open = computed({
|
|||
</span>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-500 leading-snug">
|
||||
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo
|
||||
znake, ki ne zahtevajo enkodiranja. Če npr. želite pošiljati
|
||||
šumnike, ki niso del 7-bitne abecede GSM, morate uporabiti Unicode
|
||||
enkodiranje (UCS‑2). V tem primeru je največja dolžina enega SMS
|
||||
sporočila 70 znakov (pri daljših sporočilih 67 znakov na del),
|
||||
medtem ko je pri GSM‑7 160 znakov (pri daljših sporočilih 153
|
||||
znakov na del). Razširjeni znaki (^{{ "{" }}}}\\[]~| in €) štejejo
|
||||
dvojno. Največja dovoljena dolžina po ponudniku: 640 (GSM‑7) oziroma
|
||||
Dolžina 160 znakov velja samo pri pošiljanju sporočil, ki vsebujejo znake, ki
|
||||
ne zahtevajo enkodiranja. Če npr. želite pošiljati šumnike, ki niso del
|
||||
7-bitne abecede GSM, morate uporabiti Unicode enkodiranje (UCS‑2). V tem
|
||||
primeru je največja dolžina enega SMS sporočila 70 znakov (pri daljših
|
||||
sporočilih 67 znakov na del), medtem ko je pri GSM‑7 160 znakov (pri daljših
|
||||
sporočilih 153 znakov na del). Razširjeni znaki (^{{ "{" }}}}\\[]~| in €)
|
||||
štejejo dvojno. Največja dovoljena dolžina po ponudniku: 640 (GSM‑7) oziroma
|
||||
320 (UCS‑2) znakov.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -641,10 +609,7 @@ const open = computed({
|
|||
<FormField v-slot="{ value, handleChange }" name="delivery_report">
|
||||
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
:model-value="value"
|
||||
@update:model-value="handleChange"
|
||||
/>
|
||||
<Switch :model-value="value" @update:model-value="handleChange" />
|
||||
</FormControl>
|
||||
<div class="space-y-1 leading-none">
|
||||
<FormLabel>Zahtevaj poročilo o dostavi</FormLabel>
|
||||
|
|
@ -657,10 +622,7 @@ const open = computed({
|
|||
<Button variant="outline" @click="closeSmsDialog" :disabled="processing">
|
||||
Prekliči
|
||||
</Button>
|
||||
<Button
|
||||
@click="onSubmit"
|
||||
:disabled="processing || !form.values.message"
|
||||
>
|
||||
<Button @click="onSubmit" :disabled="processing || !form.values.message">
|
||||
Pošlji
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const handleDelete = (id, label) => emit("delete", id, label);
|
|||
<template>
|
||||
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<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 flex-wrap gap-2">
|
||||
<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>
|
||||
import { Primitive } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from ".";
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { ButtonVariants } from "."
|
||||
import { Primitive } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "."
|
||||
|
||||
const props = defineProps({
|
||||
variant: { type: null, required: false },
|
||||
size: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false, default: "button" },
|
||||
});
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants["variant"]
|
||||
size?: ButtonVariants["size"]
|
||||
class?: HTMLAttributes["class"]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: "button",
|
||||
})
|
||||
</script>
|
||||
|
||||
<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>
|
||||
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from "reka-ui"
|
||||
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
defaultOpen: { type: Boolean, required: false },
|
||||
open: { type: Boolean, required: false },
|
||||
dir: { type: String, required: false },
|
||||
modal: { type: Boolean, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:open"]);
|
||||
const props = defineProps<DropdownMenuRootProps>()
|
||||
const emits = defineEmits<DropdownMenuRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,30 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Check } from "lucide-vue-next";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Check } from "lucide-vue-next"
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [Boolean, String], required: false },
|
||||
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 props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>()
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuCheckboxItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
:class=" 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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
|
|
|
|||
|
|
@ -1,59 +1,32 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
loop: { type: Boolean, required: false },
|
||||
side: { type: null, required: false },
|
||||
sideOffset: { type: Number, required: false, default: 4 },
|
||||
sideFlip: { type: Boolean, required: false },
|
||||
align: { type: null, 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",
|
||||
"closeAutoFocus",
|
||||
]);
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuContent
|
||||
v-bind="forwarded"
|
||||
: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,
|
||||
)
|
||||
"
|
||||
: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 />
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<script setup>
|
||||
import { DropdownMenuGroup } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuGroupProps } from "reka-ui"
|
||||
import { DropdownMenuGroup } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<DropdownMenuGroupProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,25 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DropdownMenuItem, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DropdownMenuItem, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
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 props = defineProps<DropdownMenuItemProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuItem
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'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',
|
||||
:class="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',
|
||||
inset && 'pl-8',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,21 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DropdownMenuLabel, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuLabelProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { DropdownMenuLabel, useForwardProps } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
inset: { type: Boolean, required: false },
|
||||
});
|
||||
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)
|
||||
"
|
||||
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuLabel>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script setup>
|
||||
import { DropdownMenuRadioGroup, useForwardPropsEmits } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from "reka-ui"
|
||||
import {
|
||||
DropdownMenuRadioGroup,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>()
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,38 +1,31 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Circle } from "lucide-vue-next";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Circle } from "lucide-vue-next"
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
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 props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<DropdownMenuRadioItem
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
:class="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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
)"
|
||||
>
|
||||
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicator>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DropdownMenuSeparator } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSeparatorProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
DropdownMenuSeparator,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<DropdownMenuSeparatorProps & {
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('-mx-1 my-1 h-px bg-muted', props.class)"
|
||||
/>
|
||||
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
<script setup>
|
||||
import { DropdownMenuSub, useForwardPropsEmits } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from "reka-ui"
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
defaultOpen: { type: Boolean, required: false },
|
||||
open: { type: Boolean, required: false },
|
||||
});
|
||||
const emits = defineEmits(["update:open"]);
|
||||
const props = defineProps<DropdownMenuSubProps>()
|
||||
const emits = defineEmits<DropdownMenuSubEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,25 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { DropdownMenuSubContent, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
DropdownMenuSubContent,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
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 props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubContent
|
||||
v-bind="forwarded"
|
||||
: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,
|
||||
)
|
||||
"
|
||||
: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 />
|
||||
</DropdownMenuSubContent>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,28 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { ChevronRight } from "lucide-vue-next";
|
||||
import { DropdownMenuSubTrigger, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuSubTriggerProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { ChevronRight } from "lucide-vue-next"
|
||||
import {
|
||||
DropdownMenuSubTrigger,
|
||||
useForwardProps,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
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 props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSubTrigger
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
:class="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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
<ChevronRight class="ml-auto h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
<script setup>
|
||||
import { DropdownMenuTrigger, useForwardProps } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { DropdownMenuTriggerProps } from "reka-ui"
|
||||
import { DropdownMenuTrigger, useForwardProps } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
disabled: { type: Boolean, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<DropdownMenuTriggerProps>()
|
||||
|
||||
const forwardedProps = useForwardProps(props);
|
||||
const forwardedProps = useForwardProps(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuTrigger class="outline-none cursor-pointer" v-bind="forwardedProps">
|
||||
<DropdownMenuTrigger class="outline-none" v-bind="forwardedProps">
|
||||
<slot />
|
||||
</DropdownMenuTrigger>
|
||||
</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>
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { useVModel } from "@vueuse/core"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
defaultValue: { type: [String, Number], required: false },
|
||||
modelValue: { type: [String, Number], required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
const emits = defineEmits<{
|
||||
(e: "update:modelValue", payload: string | number): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
:class="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',
|
||||
'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',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
/>
|
||||
)"
|
||||
>
|
||||
</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 { PaginationFirst, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { PaginationListItem } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: Number, required: true },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
|||
import { ChevronRightIcon } from "lucide-vue-next";
|
||||
import { PaginationLast, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
|||
import { ChevronRightIcon } from "lucide-vue-next";
|
||||
import { PaginationNext, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
|||
import { ChevronLeftIcon } from "lucide-vue-next";
|
||||
import { PaginationPrev, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
asChild: { type: Boolean, required: false },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { RangeCalendarCellTrigger, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
day: { type: null, required: true },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
|||
import { ChevronRight } from "lucide-vue-next";
|
||||
import { RangeCalendarNext, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
nextPage: { type: Function, required: false },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { reactiveOmit } from "@vueuse/core";
|
|||
import { ChevronLeft } from "lucide-vue-next";
|
||||
import { RangeCalendarPrev, useForwardProps } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { buttonVariants } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps({
|
||||
prevPage: { type: Function, required: false },
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { Separator } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { SeparatorProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Separator } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps({
|
||||
orientation: { type: String, required: false, default: "horizontal" },
|
||||
decorative: { type: Boolean, required: false, default: true },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
const props = withDefaults(defineProps<
|
||||
SeparatorProps & { class?: HTMLAttributes["class"] }
|
||||
>(), {
|
||||
orientation: "horizontal",
|
||||
decorative: true,
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<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>
|
||||
import { TooltipRoot, useForwardPropsEmits } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { TooltipRootEmits, TooltipRootProps } from "reka-ui"
|
||||
import { TooltipRoot, useForwardPropsEmits } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
defaultOpen: { type: Boolean, required: false },
|
||||
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 props = defineProps<TooltipRootProps>()
|
||||
const emits = defineEmits<TooltipRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipRoot v-bind="forwarded">
|
||||
<slot />
|
||||
<TooltipRoot
|
||||
v-slot="slotProps"
|
||||
data-slot="tooltip"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</TooltipRoot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,34 @@
|
|||
<script setup>
|
||||
import { reactiveOmit } from "@vueuse/core";
|
||||
import { TooltipContent, TooltipPortal, useForwardPropsEmits } from "reka-ui";
|
||||
import { cn } from "@/lib/utils";
|
||||
<script setup lang="ts">
|
||||
import type { TooltipContentEmits, TooltipContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { TooltipArrow, TooltipContent, TooltipPortal, useForwardPropsEmits } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
forceMount: { type: Boolean, required: false },
|
||||
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 props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes["class"] }>(), {
|
||||
sideOffset: 4,
|
||||
})
|
||||
|
||||
const emits = defineEmits(["escapeKeyDown", "pointerDownOutside"]);
|
||||
const emits = defineEmits<TooltipContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipPortal>
|
||||
<TooltipContent
|
||||
data-slot="tooltip-content"
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
: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,
|
||||
)
|
||||
"
|
||||
: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)"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<TooltipArrow class="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
<script setup>
|
||||
import { TooltipProvider } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { TooltipProviderProps } from "reka-ui"
|
||||
import { TooltipProvider } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
delayDuration: { type: Number, required: false },
|
||||
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 },
|
||||
});
|
||||
const props = withDefaults(defineProps<TooltipProviderProps>(), {
|
||||
delayDuration: 0,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<script setup>
|
||||
import { TooltipTrigger } from "reka-ui";
|
||||
<script setup lang="ts">
|
||||
import type { TooltipTriggerProps } from "reka-ui"
|
||||
import { TooltipTrigger } from "reka-ui"
|
||||
|
||||
const props = defineProps({
|
||||
reference: { type: null, required: false },
|
||||
asChild: { type: Boolean, required: false },
|
||||
as: { type: null, required: false },
|
||||
});
|
||||
const props = defineProps<TooltipTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TooltipTrigger v-bind="props">
|
||||
<TooltipTrigger
|
||||
data-slot="tooltip-trigger"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</TooltipTrigger>
|
||||
</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>
|
||||
<div class="py-12">
|
||||
<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="pb-3">
|
||||
<div class="pb-3 px-3">
|
||||
<SectionTitle>
|
||||
<template #title>Primeri</template>
|
||||
</SectionTitle>
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ const submitAttachSegment = () => {
|
|||
</span>
|
||||
</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">
|
||||
<SectionTitle>
|
||||
<template #title>
|
||||
|
|
@ -271,8 +271,8 @@ const submitAttachSegment = () => {
|
|||
</div>
|
||||
<div class="pt-1" :hidden="clientDetails">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1 p-3">
|
||||
<Card class="p-0">
|
||||
<div class="p-3">
|
||||
<PersonInfoGrid
|
||||
:types="types"
|
||||
:person="client.person"
|
||||
|
|
@ -285,8 +285,8 @@ const submitAttachSegment = () => {
|
|||
<!-- Case details -->
|
||||
<div class="pt-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card class="border-l-4 border-red-400">
|
||||
<div class="mx-auto max-w-4x1 p-3 flex items-center justify-between">
|
||||
<Card class="border-l-4 border-red-400 p-0">
|
||||
<div class="p-3 flex items-center justify-between">
|
||||
<SectionTitle>
|
||||
<template #title>{{ client_case.person.full_name }}</template>
|
||||
</SectionTitle>
|
||||
|
|
@ -309,8 +309,8 @@ const submitAttachSegment = () => {
|
|||
</div>
|
||||
<div class="pt-1">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1 p-3">
|
||||
<Card class="p-0">
|
||||
<div class="p-3">
|
||||
<PersonInfoGrid
|
||||
:types="types"
|
||||
tab-color="red-600"
|
||||
|
|
@ -326,8 +326,8 @@ const submitAttachSegment = () => {
|
|||
<!-- Contracts section -->
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1">
|
||||
<Card class="p-0">
|
||||
<div>
|
||||
<div class="p-3">
|
||||
<SectionTitle>
|
||||
<template #title> Pogodbe </template>
|
||||
|
|
@ -369,9 +369,9 @@ const submitAttachSegment = () => {
|
|||
<!-- Activities section -->
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1">
|
||||
<div class="flex justify-between p-4">
|
||||
<Card class="p-0">
|
||||
<div>
|
||||
<div class="flex justify-between p-3">
|
||||
<SectionTitle>
|
||||
<template #title>Aktivnosti</template>
|
||||
</SectionTitle>
|
||||
|
|
@ -410,8 +410,8 @@ const submitAttachSegment = () => {
|
|||
<!-- Documents section -->
|
||||
<div class="pt-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1">
|
||||
<Card class="p-0">
|
||||
<div>
|
||||
<div class="p-4">
|
||||
<SectionTitle>
|
||||
<template #title>Dokumenti</template>
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ function formatDate(value) {
|
|||
<!-- Header card (matches Client/Show header style) -->
|
||||
<div class="pt-6">
|
||||
<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">
|
||||
<SectionTitle>
|
||||
<template #title>
|
||||
|
|
@ -146,8 +146,8 @@ function formatDate(value) {
|
|||
</div>
|
||||
<div class="pt-1">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1 p-3">
|
||||
<Card class="p-0">
|
||||
<div class="p-3">
|
||||
<PersonInfoGrid
|
||||
:types="types"
|
||||
:person="client.person"
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ const fmtCurrency = (v) => {
|
|||
<template #header> </template>
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<CardHeader class="p-5">
|
||||
<Card class="p-0">
|
||||
<CardHeader>
|
||||
<CardTitle>Naročniki</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="p-0">
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function applySearch() {
|
|||
<template #header></template>
|
||||
<div class="pt-6">
|
||||
<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">
|
||||
<SectionTitle>
|
||||
<template #title>
|
||||
|
|
@ -77,8 +77,8 @@ function applySearch() {
|
|||
</div>
|
||||
<div class="pt-1">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<Card>
|
||||
<div class="mx-auto max-w-4x1 p-3">
|
||||
<Card class="p-0!">
|
||||
<div class="p-3">
|
||||
<PersonInfoGrid
|
||||
:types="types"
|
||||
: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>
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
import SimpleKpiCard from "./Partials/SimpleKpiCard.vue";
|
||||
import ActivityFeed from "./Partials/ActivityFeed.vue";
|
||||
import SmsOverview from "./Partials/SmsOverview.vue";
|
||||
import CompletedFieldJobsTrend from "./Partials/CompletedFieldJobsTrend.vue";
|
||||
import FieldJobsAssignedToday from "./Partials/FieldJobsAssignedToday.vue";
|
||||
import { Users, FileText, Banknote, CalendarCheck } from "lucide-vue-next";
|
||||
import AppLayout from "@/Layouts/AppLayout.vue";
|
||||
|
||||
const props = defineProps({
|
||||
kpis: Object,
|
||||
|
|
@ -41,6 +41,8 @@ const formatBalance = (amount) => {
|
|||
label="Aktivni stranke"
|
||||
:value="kpis?.active_clients"
|
||||
:icon="Users"
|
||||
icon-bg="bg-chart-2/10"
|
||||
icon-color="text-chart-2"
|
||||
/>
|
||||
<SimpleKpiCard
|
||||
label="Aktivne pogodbe"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
|
@ -20,5 +21,9 @@ export default defineConfig({
|
|||
css: {
|
||||
postcss: './postcss.config.js',
|
||||
},
|
||||
// Default resolution
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './resources/js'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user