Admin panel updated with shadcn-vue components
This commit is contained in:
@@ -2,6 +2,34 @@
|
||||
import AdminLayout from "@/Layouts/AdminLayout.vue";
|
||||
import { Link, useForm } from "@inertiajs/vue3";
|
||||
import { computed, ref } from "vue";
|
||||
import {
|
||||
UploadIcon,
|
||||
FileTextIcon,
|
||||
Power,
|
||||
PowerOffIcon,
|
||||
PencilIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
} from "lucide-vue-next";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/Components/ui/card";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Input } from "@/Components/ui/input";
|
||||
import { Label } from "@/Components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/Components/ui/select";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
import { Progress } from "@/Components/ui/progress";
|
||||
|
||||
const props = defineProps({
|
||||
templates: { type: Array, default: () => [] },
|
||||
@@ -9,7 +37,7 @@ const props = defineProps({
|
||||
|
||||
// Upload form state
|
||||
const uploadForm = useForm({ name: "", slug: "", file: null });
|
||||
const selectedSlug = ref("");
|
||||
const selectedSlug = ref(null);
|
||||
const uniqueSlugs = computed(() => {
|
||||
const s = new Set(props.templates.map((t) => t.slug));
|
||||
return Array.from(s).sort();
|
||||
@@ -60,186 +88,164 @@ const groups = computed(() => {
|
||||
|
||||
<template>
|
||||
<AdminLayout title="Dokumentne predloge">
|
||||
<div class="mb-8 space-y-6">
|
||||
<div class="space-y-6">
|
||||
<!-- Header & Upload -->
|
||||
<div class="flex flex-col xl:flex-row xl:items-start gap-6">
|
||||
<div class="flex-1 min-w-[280px]">
|
||||
<h1 class="text-2xl font-semibold tracking-tight flex items-center gap-2">
|
||||
<span>Dokumentne predloge</span>
|
||||
<span
|
||||
class="text-xs font-medium bg-gray-200 text-gray-600 px-2 py-0.5 rounded"
|
||||
>{{ groups.length }} skupin</span
|
||||
>
|
||||
</h1>
|
||||
<p class="text-sm text-gray-500 mt-1 max-w-prose">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Dokumentne predloge</h1>
|
||||
<Badge variant="secondary">{{ groups.length }} skupin</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground max-w-prose">
|
||||
Upravljaj verzije DOCX predlog. Naloži novo verzijo obstoječega sluga ali
|
||||
ustvari popolnoma novo predlogo.
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
@submit.prevent="submitUpload"
|
||||
class="flex-1 bg-white/70 backdrop-blur border rounded-lg shadow-sm p-4 flex flex-col gap-3"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-gray-700 flex items-center gap-2">
|
||||
<span class="i-lucide-upload-cloud w-4 h-4" /> Nova / nova verzija
|
||||
</h2>
|
||||
<div
|
||||
v-if="uploadForm.progress"
|
||||
class="w-40 h-1 bg-gray-200 rounded overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="h-full bg-indigo-500 transition-all"
|
||||
:style="{ width: uploadForm.progress.percentage + '%' }"
|
||||
<Card class="flex-1">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-base flex items-center gap-2">
|
||||
<UploadIcon class="h-4 w-4" />
|
||||
Nova / nova verzija
|
||||
</CardTitle>
|
||||
<Progress
|
||||
v-if="uploadForm.progress"
|
||||
:model-value="uploadForm.progress.percentage"
|
||||
class="w-40 h-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-5 gap-3 text-xs">
|
||||
<div class="md:col-span-1">
|
||||
<label class="block font-medium mb-1">Obstoječi slug</label>
|
||||
<select
|
||||
v-model="selectedSlug"
|
||||
class="select select-bordered select-sm w-full"
|
||||
>
|
||||
<option value="">(nov)</option>
|
||||
<option v-for="s in uniqueSlugs" :key="s" :value="s">{{ s }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-1">
|
||||
<label class="block font-medium mb-1">Nov slug</label>
|
||||
<input
|
||||
v-model="uploadForm.slug"
|
||||
:disabled="!!selectedSlug"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="opomin"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-1">
|
||||
<label class="block font-medium mb-1">Naziv</label>
|
||||
<input
|
||||
v-model="uploadForm.name"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="Ime predloge"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-end">
|
||||
<label class="w-full">
|
||||
<input
|
||||
id="docx-upload-input"
|
||||
@change="handleFile"
|
||||
type="file"
|
||||
accept=".docx"
|
||||
class="file-input file-input-bordered file-input-sm w-full"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-3 pt-1">
|
||||
<span class="text-[11px] text-gray-500" v-if="!uploadForm.file"
|
||||
>Izberi DOCX datoteko…</span
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm btn-primary"
|
||||
:disabled="
|
||||
uploadForm.processing ||
|
||||
!uploadForm.file ||
|
||||
(!uploadForm.slug && !selectedSlug)
|
||||
"
|
||||
>
|
||||
<span v-if="uploadForm.processing">Nalaganje…</span>
|
||||
<span v-else>Shrani verzijo</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="uploadForm.errors.file" class="text-rose-600 text-xs">
|
||||
{{ uploadForm.errors.file }}
|
||||
</div>
|
||||
</form>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form @submit.prevent="submitUpload" class="space-y-4">
|
||||
<div class="grid md:grid-cols-5 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="existing_slug">Obstoječi slug</Label>
|
||||
<Select v-model="selectedSlug">
|
||||
<SelectTrigger id="existing_slug">
|
||||
<SelectValue placeholder="(nov)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="s in uniqueSlugs" :key="s" :value="s">{{
|
||||
s
|
||||
}}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="new_slug">Nov slug</Label>
|
||||
<Input
|
||||
id="new_slug"
|
||||
v-model="uploadForm.slug"
|
||||
:disabled="!!selectedSlug"
|
||||
placeholder="opomin"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="template_name">Naziv</Label>
|
||||
<Input
|
||||
id="template_name"
|
||||
v-model="uploadForm.name"
|
||||
placeholder="Ime predloge"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-2 space-y-2">
|
||||
<Label for="docx-upload-input">DOCX datoteka</Label>
|
||||
<Input
|
||||
id="docx-upload-input"
|
||||
@change="handleFile"
|
||||
type="file"
|
||||
accept=".docx"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-3">
|
||||
<span class="text-xs text-muted-foreground" v-if="!uploadForm.file">
|
||||
Izberi DOCX datoteko…
|
||||
</span>
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
:disabled="
|
||||
uploadForm.processing ||
|
||||
!uploadForm.file ||
|
||||
(!uploadForm.slug && !selectedSlug)
|
||||
"
|
||||
>
|
||||
<UploadIcon class="h-4 w-4 mr-2" />
|
||||
{{ uploadForm.processing ? "Nalaganje…" : "Shrani verzijo" }}
|
||||
</Button>
|
||||
</div>
|
||||
<p v-if="uploadForm.errors.file" class="text-sm text-destructive">
|
||||
{{ uploadForm.errors.file }}
|
||||
</p>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Groups -->
|
||||
<div v-if="groups.length" class="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="g in groups"
|
||||
:key="g.slug"
|
||||
class="group relative flex flex-col bg-white border rounded-lg shadow-sm overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="px-4 py-3 border-b bg-gradient-to-r from-gray-50 to-white flex items-start justify-between gap-3"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<h3 class="font-medium text-sm leading-5 truncate">{{ g.name }}</h3>
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 mt-1 text-[11px] text-gray-500"
|
||||
>
|
||||
<span class="px-1.5 py-0.5 bg-gray-100 rounded">{{ g.slug }}</span>
|
||||
<span>Zadnja: v{{ g.versions[0].version }}</span>
|
||||
<span
|
||||
class="flex items-center gap-1"
|
||||
:class="
|
||||
g.versions.filter((v) => v.active).length
|
||||
? 'text-emerald-600'
|
||||
: 'text-gray-400'
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:class="
|
||||
g.versions.filter((v) => v.active).length
|
||||
? 'bg-emerald-500'
|
||||
: 'bg-gray-300'
|
||||
<Card v-for="g in groups" :key="g.slug">
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<CardTitle class="text-base truncate">{{ g.name }}</CardTitle>
|
||||
<CardDescription class="flex flex-wrap items-center gap-2 mt-1">
|
||||
<Badge variant="secondary" class="text-xs">{{ g.slug }}</Badge>
|
||||
<span class="text-xs">Zadnja: v{{ g.versions[0].version }}</span>
|
||||
<Badge
|
||||
:variant="
|
||||
g.versions.filter((v) => v.active).length ? 'default' : 'outline'
|
||||
"
|
||||
/>
|
||||
{{ g.versions.filter((v) => v.active).length }} aktivnih
|
||||
</span>
|
||||
class="text-xs"
|
||||
>
|
||||
{{ g.versions.filter((v) => v.active).length }} aktivnih
|
||||
</Badge>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button size="sm" variant="ghost" as-child>
|
||||
<Link :href="route('admin.document-templates.show', g.versions[0].id)">
|
||||
Detalji
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<Link
|
||||
:href="route('admin.document-templates.show', g.versions[0].id)"
|
||||
class="text-xs text-indigo-600 hover:underline whitespace-nowrap mt-1"
|
||||
>Detalji</Link
|
||||
>
|
||||
</div>
|
||||
<div class="p-3 flex-1 flex flex-col gap-2">
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-col gap-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div v-for="v in g.versions" :key="v.id" class="flex items-center gap-1">
|
||||
<Link
|
||||
:href="route('admin.document-templates.edit', v.id)"
|
||||
class="px-2 py-0.5 rounded-md border text-[11px] font-medium transition-colors"
|
||||
:class="
|
||||
v.active
|
||||
? 'border-emerald-500/60 bg-emerald-50 text-emerald-700 hover:bg-emerald-100'
|
||||
: 'border-gray-300 bg-white text-gray-600 hover:bg-gray-50'
|
||||
"
|
||||
>v{{ v.version }}</Link
|
||||
<Button
|
||||
size="sm"
|
||||
:variant="v.active ? 'default' : 'outline'"
|
||||
class="h-7 px-2 text-xs"
|
||||
as-child
|
||||
>
|
||||
<button
|
||||
<Link :href="route('admin.document-templates.edit', v.id)">
|
||||
v{{ v.version }}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
:variant="v.active ? 'destructive' : 'outline'"
|
||||
class="h-7 w-7"
|
||||
@click="toggle(v.id)"
|
||||
class="rounded-md border px-1.5 py-0.5 text-[10px] font-medium transition-colors"
|
||||
:class="
|
||||
v.active
|
||||
? 'bg-amber-500 border-amber-500 text-white hover:bg-amber-600'
|
||||
: 'bg-gray-100 border-gray-300 text-gray-600 hover:bg-gray-200'
|
||||
"
|
||||
>
|
||||
{{ v.active ? "✕" : "✓" }}
|
||||
</button>
|
||||
<XIcon v-if="v.active" class="h-3 w-3" />
|
||||
<CheckIcon v-else class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto pt-2 border-t flex justify-end">
|
||||
<Link
|
||||
:href="route('admin.document-templates.edit', g.versions[0].id)"
|
||||
class="text-[11px] text-indigo-600 hover:underline"
|
||||
>Uredi zadnjo verzijo →</Link
|
||||
>
|
||||
<div class="pt-2 border-t flex justify-end">
|
||||
<Button size="sm" variant="link" class="h-auto p-0" as-child>
|
||||
<Link :href="route('admin.document-templates.edit', g.versions[0].id)">
|
||||
Uredi zadnjo verziju →
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<p v-else class="text-sm text-gray-500">Ni predlog.</p>
|
||||
<p v-else class="text-sm text-muted-foreground">Ni predlog.</p>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user