freekake_webapp/pages/character/skins/[id].vue
2025-07-31 13:56:11 +07:00

268 lines
12 KiB
Vue
Raw Permalink 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">Skin Karakter</a>
</li>
<li class="before:mr-2 before:content-['/'] rtl:before:ml-2">
<NuxtLink to="/character/skins/list" class="text-primary hover:underline">Daftar Skin Karakter</NuxtLink>
</li>
<li class="before:mr-2 before:content-['/'] rtl:before:ml-2">
<span>Edit Skin Karakter</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 Skin Karakter</h5>
<NuxtLink to="/character/skins/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 class="space-y-5" @submit.prevent="submitForm()">
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 gap-2">
<div :class="{ 'has-error': $validate.form.name.$error, 'has-success': isSubmitted && !$validate.form.name.$error }">
<label for="name">Nama Skin</label>
<input id="name" type="text" class="form-input" v-model="form.name" />
<template v-if="isSubmitted && $validate.form.name.$error">
<p class="text-danger mt-1">Nama Skin Karakter harus diisi</p>
</template>
</div>
<div :class="{ 'has-error': $validate.form.character.$error, 'has-success': isSubmitted && !$validate.form.character.$error }">
<label for="character">Karakter</label>
<multiselect id="character"
v-model="form.character"
:options="characters?.results"
class="custom-multiselect"
:searchable="true"
placeholder="Pilih karakter"
selected-label=""
select-label=""
deselect-label=""
label="name"
track-by="id"
></multiselect>
<template v-if="isSubmitted && $validate.form.character.$error">
<p class="text-danger mt-1">Karakter 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.description.$error, 'has-success': isSubmitted && !$validate.form.description.$error }">
<label for="description">Deskripsi Karakter</label>
<textarea id="description" rows="3" class="form-textarea" v-model="form.description"></textarea>
<template v-if="isSubmitted && $validate.form.description.$error">
<p class="text-danger mt-1">Deskripsi karakter 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 diisi</p>
</template>
<div class="custom-file-container__image-preview"></div>
</div>
<div class="custom-file-container" data-upload-id="featuredIcon"
:class="{ 'has-error': $validate.form.featured_icon.$error, 'has-success': isSubmitted && !$validate.form.featured_icon.$error }">
<div class="label-container">
<label for="featured_icon">Ikon </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_icon" type="file" class="custom-file-container__custom-file__custom-file-input"
accept="image/png" @change="handleIconChange" />
<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_icon.$error">
<p class="text-danger mt-1">Gambar ikon karakter harus diisi</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 { helpers, integer, minValue,required } 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 Skin Karakter' });
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 isSubmitted = ref(false);
const rules = {
form: {
name: { required },
description: { required },
character: { required },
featured_image: { imageType, maxFileSize },
featured_icon: { imageType, maxFileSize },
}
};
const router = useRouter();
const route = useRoute();
const config = useRuntimeConfig();
const { $api } = useNuxtApp();
const isLoading = ref(false);
const featuredImageUploader = ref(null);
const featuredIconUploader = ref(null);
const params = reactive({
search: null,
current_page: 1,
pagesize: 50,
sort_column: 'name',
sort_direction: 'asc',
});
const { data: characters } = await useAsyncData('characters',
() => {
return $api(`/character/characters/`, {
params: {
page: params.current_page,
page_size: params.pagesize,
}})
}, {
watch: [params]
}
);
const { data: form, refresh } = await useAsyncData('skin-get',
() => $api(`/character/character-skins/${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: '',
},
});
featuredIconUploader.value = new FileUploadWithPreview('featuredIcon', {
images: {
baseImage: form.value?.featured_icon || '/assets/images/file-preview.svg',
backgroundImage: '',
},
});
});
const submitForm = async () => {
isSubmitted.value = true;
$validate.value.form.$touch();
if ($validate.value.form.$invalid) {
return false;
}
const formData = new FormData();
formData.append('name', form.value.name);
formData.append('character', form.value.character.id);
formData.append('description', form.value.description);
if (form.value.featured_image && typeof form.value.featured_image !== 'string') {
formData.append('featured_image', form.value.featured_image);
}
if (form.value.featured_icon && typeof form.value.featured_icon !== 'string') {
formData.append('featured_icon', form.value.featured_icon);
}
isLoading.value = true;
await $api(`/character/character-skins/${route.params.id}/`, {
method: 'PUT',
body: formData
}).then(() => {
router.push({ path: "/character/skins/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 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>