freekake_webapp/pages/entertainment/manga/[id].vue
2025-10-13 14:35:26 +07:00

309 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<ul class="flex space-x-2 rtl:space-x-reverse">
<li>
<a href="javascript:;" class="text-primary hover:underline">Entertainment</a>
</li>
<li class="before:mr-2 before:content-['/'] rtl:before:ml-2">
<NuxtLink to="/entertainment/manga/list" class="text-primary hover:underline">Manga</NuxtLink>
</li>
<li class="before:mr-2 before:content-['/'] rtl:before:ml-2">
<span>Edit Manga</span>
</li>
</ul>
<div class="grid grid-cols-1 gap-6 pt-5">
<div class="panel">
<div class="mb-5 flex items-center justify-between">
<h5 class="text-lg font-semibold dark:text-white-light">Edit Karakter</h5>
<NuxtLink to="/entertainment/manga/list" class="dark:text-white-light btn btn-dark !py-1">
<icon-arrow-backward class="me-1" />
Daftar
</NuxtLink>
</div>
<div class="mb-5">
<form v-if="form" class="space-y-5" @submit.prevent="submitForm()">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 gap-2">
<div :class="{ 'has-error': $validate.form.title.$error, 'has-success': isSubmitted && !$validate.form.title.$error }">
<label for="title">Judul Manga</label>
<input id="title" type="text" class="form-input" v-model="form.title" />
<template v-if="isSubmitted && $validate.form.title.$error">
<p class="text-danger mt-1">Judul Manga harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.slug.$error, 'has-success': isSubmitted && !$validate.form.slug.$error }">
<label for="slug">Slug Manga</label>
<input id="slug" type="text" class="form-input" v-model="form.slug" />
<template v-if="isSubmitted && $validate.form.slug.$error">
<p class="text-danger mt-1">Slug Manga harus diisi</p>
</template>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 gap-2">
<div :class="{ 'has-error': $validate.form.genre.$error, 'has-success': isSubmitted && !$validate.form.genre.$error }">
<label for="genre">Genre</label>
<multiselect id="genre"
v-model="form.genre"
:options="genreOptions"
class="custom-multiselect"
:searchable="true"
placeholder="Pilih genre manga"
selected-label=""
select-label=""
deselect-label=""
label="label"
track-by="value"
></multiselect>
<template v-if="isSubmitted && $validate.form.genre.$error">
<p class="text-danger mt-1">Genre Manga harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.coin.$error, 'has-success': isSubmitted && !$validate.form.coin.$error }">
<label for="coin">Coin</label>
<input id="coin" type="number" class="form-input" v-model="form.coin" />
<template v-if="isSubmitted && $validate.form.coin.$error">
<p class="text-danger mt-1">Coin Manga harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.color.$error, 'has-success': isSubmitted && !$validate.form.color.$error }">
<label for="color">Color</label>
<input id="color" type="text" class="form-input" v-model="form.color" />
<template v-if="isSubmitted && $validate.form.color.$error">
<p class="text-danger mt-1">Color Manga harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.status.$error, 'has-success': isSubmitted && !$validate.form.status.$error }">
<label for="status">Status</label>
<multiselect id="status"
v-model="form.status"
:options="statusOptions"
class="custom-multiselect"
:searchable="true"
placeholder="Pilih status manga"
selected-label=""
select-label=""
deselect-label=""
label="label"
track-by="value"
></multiselect>
<template v-if="isSubmitted && $validate.form.status.$error">
<p class="text-danger mt-1">Status Manga harus diisi</p>
</template>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-1 gap-2">
<div :class="{ 'has-error': $validate.form.synopsis.$error, 'has-success': isSubmitted && !$validate.form.synopsis.$error }">
<label for="synopsis">Synopsis</label>
<textarea id="synopsis" rows="3" class="form-textarea" v-model="form.synopsis"></textarea>
<template v-if="isSubmitted && $validate.form.synopsis.$error">
<p class="text-danger mt-1">Deskripsi karakter harus diisi</p>
</template>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 gap-2">
<div :class="{ 'has-error': $validate.form.posted_at.$error, 'has-success': isSubmitted && !$validate.form.posted_at.$error }">
<label for="posted_at">Tanggal Unggah</label>
<input id="posted_at" type="datetime-local" class="form-input" v-model="form.posted_at" />
<template v-if="isSubmitted && $validate.form.posted_at.$error">
<p class="text-danger mt-1">Tanggal unggah Manga harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.archived_at.$error, 'has-success': isSubmitted && !$validate.form.archived_at.$error }">
<label for="archived_at">Tanggal Arsip</label>
<input id="archived_at" type="datetime-local" class="form-input" v-model="form.archived_at" />
<template v-if="isSubmitted && $validate.form.archived_at.$error">
<p class="text-danger mt-1">Tanggal arsip Manga harus diisi</p>
</template>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="custom-file-container" data-upload-id="featuredImage"
:class="{ 'has-error': $validate.form.featured_image.$error, 'has-success': isSubmitted && !$validate.form.featured_image.$error }">
<div class="label-container">
<label for="featured_image">Gambar </label> <a href="javascript:;" class="custom-file-container__image-clear" title="Clear Image">×</a>
</div>
<label class="custom-file-container__custom-file" >
<input id="featured_image" type="file" class="custom-file-container__custom-file__custom-file-input"
accept="image/png" @change="handleImageChange" />
<input type="hidden" name="MAX_FILE_SIZE" value="10485760" />
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
</label>
<template v-if="isSubmitted && $validate.form.featured_image.$error">
<p class="text-danger mt-1">Gambar karakter harus diupload dengan file berjenis PNG atau JPEG dengan ukuran maksimal 1 MB</p>
</template>
<div class="custom-file-container__image-preview"></div>
</div>
</div>
<div class="flex items-center ltr:ml-auto mt-8">
<button type="submit" class="btn btn-success !py-1" :disabled="isLoading"><icon-save class="me-1" /> Simpan</button>
<button type="reset" class="btn btn-dark !py-1 ml-1" @click="resetForm"><icon-restore class="me-1" />Reset</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useVuelidate } from '@vuelidate/core';
import { required, helpers } from '@vuelidate/validators';
import Multiselect from '@suadelabs/vue3-multiselect';
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
import '@/assets/css/file-upload-preview.css';
useHead({ title: 'Edit Karakter' });
const isSubmitted = ref(false);
const imageType = helpers.withMessage('Format gambar harus PNG atau JPG', (value) => {
if (!value) return true;
if (typeof value === 'string') return true;
return ['image/png', 'image/jpeg', 'image/jpg'].includes(value.type);
});
const maxFileSize = helpers.withMessage('Ukuran gambar tidak boleh lebih dari 1MB', (value) => {
if (!value) return true;
if (typeof value === 'string') return true;
return value.size <= 1048576; // 1MB dalam byte
});
const rules = {
form: {
title: { required },
slug: { required },
genre: { required },
coin: { required },
color: { required },
status: { required },
synopsis: { required },
posted_at: { required },
archived_at: { required },
featured_image: { imageType, maxFileSize },
}
};
const router = useRouter();
const route = useRoute();
const config = useRuntimeConfig();
const { $api } = useNuxtApp();
const genreOptions = [{ "value": "komedi", "label": "Komedi" }, { "value": "petualangan", "label": "Petualangan" }, { "value": "fiksi ilmiah", "label": "Fiksi Ilmiah" }, { "value": "fantasi", "label": "Fantasi" }, { "value": "kehidupan sehari-hari", "label": "Kehidupan Sehari-hari" } ]
const statusOptions = [{ "value": "draft", "label": "Draft" }, { "value": "published", "label": "Dipublikasikan" }, { "value": "archived", "label": "Diarsipkan" } ]
const isLoading = ref(false);
const featuredImageUploader = ref(null);
const { data: form, refresh } = await useAsyncData('manga-get',
() => $api(`/entertainment/manga/${route.params.id}/`, {}),
);
const $validate = useVuelidate(rules, { form });
onMounted(async () => {
if (!form.value) return;
const fileupload = await import('file-upload-with-preview');
let FileUploadWithPreview = fileupload.default;
featuredImageUploader.value =new FileUploadWithPreview('featuredImage', {
images: {
baseImage: form.value.featured_image || '/assets/images/file-preview.svg',
backgroundImage: '',
},
});
if (form.value.genre) {
const foundGenre = genreOptions.find(opt => opt.value === form.value.genre);
if (foundGenre) form.value.genre = foundGenre;
}
if (form.value.status) {
const foundStatus = statusOptions.find(opt => opt.value === form.value.status);
if (foundStatus) form.value.status = foundStatus;
}
if (form.value.posted_at) {
form.value.posted_at = form.value.posted_at.replace('Z', '').slice(0, 16);
}
if (form.value.archived_at) {
form.value.archived_at = form.value.archived_at.replace('Z', '').slice(0, 16);
}
});
const submitForm = async () => {
isSubmitted.value = true;
$validate.value.form.$touch();
if ($validate.value.form.$invalid) {
return false;
}
const formData = new FormData();
formData.append('title', form.value.title);
formData.append('slug', form.value.slug);
formData.append('genre', form.value.genre.value);
formData.append('coin', form.value.coin);
formData.append('color', form.value.color);
formData.append('status', form.value.status.value);
formData.append('synopsis', form.value.synopsis);
formData.append('posted_at', new Date(form.value.posted_at).toISOString());
formData.append('archived_at', new Date(form.value.archived_at).toISOString());
if (form.value.featured_image && typeof form.value.featured_image !== 'string') {
formData.append('featured_image', form.value.featured_image);
}
isLoading.value = true;
await $api(`/entertainment/manga/${route.params.id}/`, {
method: 'PUT',
body: formData
}).then(() => {
router.push({ path: "/entertainment/manga/list" });
})
.catch((error) => {
console.log(error);
})
.finally(() => {
isLoading.value = false;
});
}
function handleImageChange(event: { target: { files: any[]; }; }) {
const file = event.target.files[0]
if (!file) return
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
if (!allowed.includes(file.type)) {
alert('Hanya PNG atau JPG yang diizinkan')
return
}
form.value.featured_image = file
}
function handleIconChange(event: { target: { files: any[]; }; }) {
const file = event.target.files[0]
if (!file) return
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
if (!allowed.includes(file.type)) {
alert('Hanya PNG atau JPG yang diizinkan')
return
}
form.value.featured_icon = file
}
const resetForm = () => {
refresh()
isSubmitted.value = false;
$validate.value.form.$reset();
featuredImageUploader?.value?.clearPreviewPanel();
featuredIconUploader?.value?.clearPreviewPanel();
};
</script>