246 lines
6.8 KiB
Vue
246 lines
6.8 KiB
Vue
<script setup>
|
|
import CreateDialog from "@/Components/Dialogs/CreateDialog.vue";
|
|
import { useForm } from "vee-validate";
|
|
import { toTypedSchema } from "@vee-validate/zod";
|
|
import * as z from "zod";
|
|
import { ref, watch } from "vue";
|
|
import { router } from "@inertiajs/vue3";
|
|
import {
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/Components/ui/form";
|
|
import { Input } from "@/Components/ui/input";
|
|
import { Textarea } from "@/Components/ui/textarea";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/Components/ui/select";
|
|
import { Switch } from "@/Components/ui/switch";
|
|
|
|
const props = defineProps({
|
|
show: { type: Boolean, default: false },
|
|
postUrl: { type: String, required: true },
|
|
// Optional list of contracts to allow attaching the document directly to a contract
|
|
// Each item should have at least: { uuid, reference }
|
|
contracts: { type: Array, default: () => [] },
|
|
});
|
|
const emit = defineEmits(["close", "uploaded"]);
|
|
|
|
const MAX_SIZE = 25 * 1024 * 1024; // 25MB
|
|
const ALLOWED_EXTS = [
|
|
"doc",
|
|
"docx",
|
|
"pdf",
|
|
"txt",
|
|
"csv",
|
|
"xls",
|
|
"xlsx",
|
|
"jpeg",
|
|
"jpg",
|
|
"png",
|
|
];
|
|
|
|
const formSchema = toTypedSchema(
|
|
z.object({
|
|
name: z.string().min(1, "Ime je obvezno"),
|
|
description: z.string().optional(),
|
|
file: z.instanceof(File).refine((file) => file.size > 0, "Izberite datoteko"),
|
|
is_public: z.boolean().default(true),
|
|
contract_uuid: z.string().nullable().optional(),
|
|
})
|
|
);
|
|
|
|
const form = useForm({
|
|
validationSchema: formSchema,
|
|
initialValues: {
|
|
name: "",
|
|
description: "",
|
|
file: null,
|
|
is_public: true,
|
|
contract_uuid: null,
|
|
},
|
|
});
|
|
|
|
const localError = ref("");
|
|
|
|
watch(
|
|
() => props.show,
|
|
(v) => {
|
|
if (!v) return;
|
|
localError.value = "";
|
|
form.resetForm();
|
|
}
|
|
);
|
|
|
|
const onFileChange = (e) => {
|
|
localError.value = "";
|
|
const f = e.target.files?.[0];
|
|
if (!f) {
|
|
form.setFieldValue("file", null);
|
|
return;
|
|
}
|
|
const ext = (f.name.split(".").pop() || "").toLowerCase();
|
|
if (!ALLOWED_EXTS.includes(ext)) {
|
|
localError.value = "Nepodprta vrsta datoteke. Dovoljeno: " + ALLOWED_EXTS.join(", ");
|
|
e.target.value = "";
|
|
form.setFieldValue("file", null);
|
|
return;
|
|
}
|
|
if (f.size > MAX_SIZE) {
|
|
localError.value = "Datoteka je prevelika. Največja velikost je 25MB.";
|
|
e.target.value = "";
|
|
form.setFieldValue("file", null);
|
|
return;
|
|
}
|
|
form.setFieldValue("file", f);
|
|
if (!form.values.name) {
|
|
form.setFieldValue("name", f.name.replace(/\.[^.]+$/, ""));
|
|
}
|
|
};
|
|
|
|
const submit = form.handleSubmit(async (values) => {
|
|
localError.value = "";
|
|
if (!values.file) {
|
|
localError.value = "Prosimo izberite datoteko.";
|
|
return;
|
|
}
|
|
const ext = (values.file.name.split(".").pop() || "").toLowerCase();
|
|
if (!ALLOWED_EXTS.includes(ext)) {
|
|
localError.value = "Nepodprta vrsta datoteke. Dovoljeno: " + ALLOWED_EXTS.join(", ");
|
|
return;
|
|
}
|
|
if (values.file.size > MAX_SIZE) {
|
|
localError.value = "Datoteka je prevelika. Največja velikost je 25MB.";
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append("name", values.name);
|
|
formData.append("description", values.description || "");
|
|
formData.append("file", values.file);
|
|
formData.append("is_public", values.is_public ? "1" : "0");
|
|
if (values.contract_uuid) {
|
|
formData.append("contract_uuid", values.contract_uuid);
|
|
}
|
|
|
|
router.post(props.postUrl, formData, {
|
|
forceFormData: true,
|
|
onSuccess: () => {
|
|
emit("uploaded");
|
|
emit("close");
|
|
form.resetForm();
|
|
},
|
|
onError: (errors) => {
|
|
// Set form errors if any
|
|
if (errors.name) form.setFieldError("name", errors.name);
|
|
if (errors.description) form.setFieldError("description", errors.description);
|
|
if (errors.file) form.setFieldError("file", errors.file);
|
|
if (errors.contract_uuid) form.setFieldError("contract_uuid", errors.contract_uuid);
|
|
},
|
|
});
|
|
});
|
|
|
|
const close = () => emit("close");
|
|
|
|
const onConfirm = () => {
|
|
submit();
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<CreateDialog
|
|
:show="props.show"
|
|
title="Dodaj dokument"
|
|
max-width="lg"
|
|
confirm-text="Naloži"
|
|
:processing="!!form.isSubmitting.value"
|
|
:disabled="!form.values.file"
|
|
@close="close"
|
|
@confirm="onConfirm"
|
|
>
|
|
<form @submit.prevent="submit" class="space-y-4">
|
|
<FormField
|
|
v-if="props.contracts && props.contracts.length"
|
|
v-slot="{ value, handleChange }"
|
|
name="contract_uuid"
|
|
>
|
|
<FormItem>
|
|
<FormLabel>Pripiši k</FormLabel>
|
|
<Select :model-value="value" @update:model-value="handleChange">
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Primer" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem :value="null">Primer</SelectItem>
|
|
<SelectItem v-for="c in props.contracts" :key="c.uuid" :value="c.uuid">
|
|
Pogodba: {{ c.reference }}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="name">
|
|
<FormItem>
|
|
<FormLabel>Ime</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
id="doc_name"
|
|
v-bind="componentField"
|
|
class="w-full max-w-full overflow-hidden text-ellipsis"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="description">
|
|
<FormItem>
|
|
<FormLabel>Opis</FormLabel>
|
|
<FormControl>
|
|
<Textarea id="doc_desc" v-bind="componentField" rows="3" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ value, handleChange }" name="file">
|
|
<FormItem>
|
|
<FormLabel>Datoteka (max 25MB)</FormLabel>
|
|
<FormControl class="flex w-full">
|
|
<Input
|
|
id="doc_file"
|
|
type="file"
|
|
@change="onFileChange"
|
|
accept=".doc,.docx,.pdf,.txt,.csv,.xls,.xlsx,.jpeg,.jpg,.png"
|
|
class="min-w-0 w-full"
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
<div v-if="localError" class="text-sm text-red-600 mt-1">{{ localError }}</div>
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ value, handleChange }" name="is_public">
|
|
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
|
|
<FormControl>
|
|
<Switch :model-value="value" @update:model-value="handleChange" />
|
|
</FormControl>
|
|
<div class="space-y-1 leading-none">
|
|
<FormLabel>Javno</FormLabel>
|
|
</div>
|
|
</FormItem>
|
|
</FormField>
|
|
</form>
|
|
</CreateDialog>
|
|
</template>
|