234 lines
7.9 KiB
Vue
234 lines
7.9 KiB
Vue
<script setup>
|
|
import { Input } from "@/Components/ui/input";
|
|
import { Badge } from "@/Components/ui/badge";
|
|
import { Card, CardContent } from "@/Components/ui/card";
|
|
import { Separator } from "@/Components/ui/separator";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/Components/ui/dialog";
|
|
import axios from "axios";
|
|
import { debounce } from "lodash";
|
|
import { SearchIcon, XIcon } from "lucide-vue-next";
|
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
|
import { Link } from "@inertiajs/vue3";
|
|
|
|
const props = defineProps({
|
|
open: { type: Boolean, default: false },
|
|
});
|
|
const emit = defineEmits(["update:open"]);
|
|
|
|
const query = ref("");
|
|
const result = ref({ clients: [], client_cases: [] });
|
|
const isOpen = ref(props.open);
|
|
|
|
watch(
|
|
() => props.open,
|
|
(v) => {
|
|
isOpen.value = v;
|
|
if (v) focusInput();
|
|
}
|
|
);
|
|
watch(isOpen, (v) => emit("update:open", v));
|
|
|
|
const searching = debounce((value) => {
|
|
if (!value || !value.trim()) {
|
|
result.value = { clients: [], client_cases: [] };
|
|
return;
|
|
}
|
|
axios
|
|
.get(route("search"), { params: { query: value, limit: 8, tag: "" } })
|
|
.then((res) => {
|
|
result.value = res.data;
|
|
})
|
|
.catch(() => {});
|
|
}, 250);
|
|
|
|
watch(
|
|
() => query.value,
|
|
(val) => searching(val)
|
|
);
|
|
|
|
const inputWrap = ref(null);
|
|
const focusInput = () =>
|
|
setTimeout(() => inputWrap.value?.querySelector("input")?.focus(), 0);
|
|
|
|
function onKeydown(e) {
|
|
if (e.key === "Escape") {
|
|
isOpen.value = false;
|
|
}
|
|
}
|
|
onMounted(() => window.addEventListener("keydown", onKeydown));
|
|
onUnmounted(() => window.removeEventListener("keydown", onKeydown));
|
|
</script>
|
|
<template>
|
|
<Dialog :open="isOpen" @update:open="(v) => (isOpen = v)">
|
|
<DialogContent class="max-w-3xl p-0 gap-0 [&>button]:hidden">
|
|
<div class="p-4 border-b" ref="inputWrap">
|
|
<div class="relative">
|
|
<SearchIcon
|
|
class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
|
|
/>
|
|
<Input
|
|
v-model="query"
|
|
placeholder="Išči po naročnikih ali primerih (ESC za zapiranje)"
|
|
class="w-full pl-10 pr-16"
|
|
/>
|
|
<button
|
|
v-if="query"
|
|
@click="query = ''"
|
|
class="absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded hover:bg-accent"
|
|
>
|
|
<XIcon class="h-4 w-4 text-muted-foreground" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="max-h-[65vh] overflow-y-auto">
|
|
<div
|
|
v-if="!query"
|
|
class="p-8 text-sm text-muted-foreground text-center space-y-2"
|
|
>
|
|
<p>Začni tipkati za iskanje.</p>
|
|
<p class="text-xs">
|
|
Namig: uporabi <Badge variant="secondary" class="font-mono">Ctrl</Badge> +
|
|
<Badge variant="secondary" class="font-mono">K</Badge>
|
|
</p>
|
|
</div>
|
|
<div v-else class="space-y-4 p-4">
|
|
<!-- Clients Results -->
|
|
<div v-if="result.clients.length">
|
|
<div
|
|
class="flex items-center justify-between pb-2 text-xs font-semibold tracking-wide uppercase text-muted-foreground"
|
|
>
|
|
<span>Naročniki</span>
|
|
<Badge variant="secondary">{{ result.clients.length }}</Badge>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<Link
|
|
v-for="client in result.clients"
|
|
:key="client.client_uuid"
|
|
:href="route('client.show', { uuid: client.client_uuid })"
|
|
class="group flex items-center gap-3 w-full rounded-lg px-3 py-2 text-sm hover:bg-accent transition"
|
|
@click="isOpen = false"
|
|
>
|
|
<Badge
|
|
variant="outline"
|
|
class="shrink-0 w-6 h-6 flex items-center justify-center"
|
|
>C</Badge
|
|
>
|
|
<span class="font-medium">{{ client.full_name }}</span>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator v-if="result.clients.length && result.client_cases.length" />
|
|
|
|
<!-- Client Cases Results -->
|
|
<div v-if="result.client_cases.length">
|
|
<div
|
|
class="flex items-center justify-between pb-2 text-xs font-semibold tracking-wide uppercase text-muted-foreground"
|
|
>
|
|
<span>Primeri</span>
|
|
<Badge variant="secondary">{{ result.client_cases.length }}</Badge>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Card
|
|
v-for="clientcase in result.client_cases"
|
|
:key="clientcase.case_uuid"
|
|
class="hover:shadow-md transition p-0"
|
|
>
|
|
<CardContent class="p-3 space-y-2">
|
|
<div class="space-y-1">
|
|
<Link
|
|
:href="
|
|
route('clientCase.show', {
|
|
client_case: clientcase.case_uuid,
|
|
})
|
|
"
|
|
class="text-sm font-medium hover:underline block"
|
|
@click="isOpen = false"
|
|
>
|
|
{{ clientcase.full_name }}
|
|
</Link>
|
|
<div
|
|
v-if="clientcase.client_full_name"
|
|
class="text-xs text-muted-foreground"
|
|
>
|
|
Naročnik: {{ clientcase.client_full_name }}
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="clientcase.contract_reference"
|
|
class="flex items-center gap-1"
|
|
>
|
|
<Badge variant="outline" class="font-mono text-xs">
|
|
{{ clientcase.contract_reference }}
|
|
</Badge>
|
|
</div>
|
|
<div
|
|
v-if="
|
|
clientcase.contract_segments && clientcase.contract_segments.length
|
|
"
|
|
class="flex flex-wrap gap-1"
|
|
>
|
|
<Link
|
|
v-for="seg in clientcase.contract_segments"
|
|
:key="seg.id || seg.name || seg"
|
|
:href="
|
|
route('clientCase.show', {
|
|
client_case: clientcase.case_uuid,
|
|
}) +
|
|
'?segment=' +
|
|
(seg.id || seg)
|
|
"
|
|
@click="isOpen = false"
|
|
>
|
|
<Badge variant="secondary" class="text-xs uppercase">
|
|
{{ seg.name || seg }}
|
|
</Badge>
|
|
</Link>
|
|
</div>
|
|
<div
|
|
v-else-if="
|
|
clientcase.case_segments && clientcase.case_segments.length
|
|
"
|
|
class="flex flex-wrap gap-1"
|
|
>
|
|
<Link
|
|
v-for="seg in clientcase.case_segments"
|
|
:key="seg.id || seg.name"
|
|
:href="
|
|
route('clientCase.show', {
|
|
client_case: clientcase.case_uuid,
|
|
}) +
|
|
'?segment=' +
|
|
(seg.id || seg)
|
|
"
|
|
@click="isOpen = false"
|
|
>
|
|
<Badge variant="outline" class="text-xs uppercase">
|
|
{{ seg.name }}
|
|
</Badge>
|
|
</Link>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No Results -->
|
|
<div
|
|
v-if="!result.clients.length && !result.client_cases.length"
|
|
class="p-8 text-center text-sm text-muted-foreground"
|
|
>
|
|
Ni rezultatov.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|