karakter
This commit is contained in:
parent
9293c4cde5
commit
f49a7e9fc7
147
nuxt.config.ts
147
nuxt.config.ts
@ -1,74 +1,85 @@
|
||||
export default defineNuxtConfig({
|
||||
app: {
|
||||
head: {
|
||||
title: 'Freekake Admin',
|
||||
titleTemplate: '%s | Freekake Admin',
|
||||
htmlAttrs: {
|
||||
lang: 'en',
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no',
|
||||
},
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' },
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.png' },
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&display=swap',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
title: 'Freekake Admin',
|
||||
titleTemplate: '%s | Freekake Admin',
|
||||
htmlAttrs: {
|
||||
lang: 'en',
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{
|
||||
name: 'viewport',
|
||||
content: 'width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no',
|
||||
},
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
{ name: 'format-detection', content: 'telephone=no' },
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.png' },
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800&display=swap',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
css: ['~/assets/css/app.css'],
|
||||
postcss: {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
modules: ['@pinia/nuxt', '@nuxtjs/i18n'],
|
||||
css: ['~/assets/css/app.css'],
|
||||
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'da', file: 'da.json' },
|
||||
{ code: 'de', file: 'de.json' },
|
||||
{ code: 'el', file: 'fr.json' },
|
||||
{ code: 'en', file: 'en.json' },
|
||||
{ code: 'es', file: 'es.json' },
|
||||
{ code: 'fr', file: 'fr.json' },
|
||||
{ code: 'hu', file: 'hu.json' },
|
||||
{ code: 'it', file: 'it.json' },
|
||||
{ code: 'ja', file: 'ja.json' },
|
||||
{ code: 'pl', file: 'pl.json' },
|
||||
{ code: 'pt', file: 'pt.json' },
|
||||
{ code: 'ru', file: 'ru.json' },
|
||||
{ code: 'sv', file: 'sv.json' },
|
||||
{ code: 'tr', file: 'tr.json' },
|
||||
{ code: 'zh', file: 'zh.json' },
|
||||
{ code: 'ae', file: 'ae.json' },
|
||||
],
|
||||
lazy: true,
|
||||
defaultLocale: 'en',
|
||||
strategy: 'no_prefix',
|
||||
langDir: 'locales/',
|
||||
},
|
||||
vite: {
|
||||
optimizeDeps: { include: ['quill'] },
|
||||
},
|
||||
router: {
|
||||
options: { linkExactActiveClass: 'active' },
|
||||
},
|
||||
compatibilityDate: '2024-09-21',
|
||||
postcss: {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: process.env.API_BASE_URL,
|
||||
},
|
||||
modules: ['@pinia/nuxt', '@nuxtjs/i18n'],
|
||||
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'da', file: 'da.json' },
|
||||
{ code: 'de', file: 'de.json' },
|
||||
{ code: 'el', file: 'fr.json' },
|
||||
{ code: 'en', file: 'en.json' },
|
||||
{ code: 'es', file: 'es.json' },
|
||||
{ code: 'fr', file: 'fr.json' },
|
||||
{ code: 'hu', file: 'hu.json' },
|
||||
{ code: 'it', file: 'it.json' },
|
||||
{ code: 'ja', file: 'ja.json' },
|
||||
{ code: 'pl', file: 'pl.json' },
|
||||
{ code: 'pt', file: 'pt.json' },
|
||||
{ code: 'ru', file: 'ru.json' },
|
||||
{ code: 'sv', file: 'sv.json' },
|
||||
{ code: 'tr', file: 'tr.json' },
|
||||
{ code: 'zh', file: 'zh.json' },
|
||||
{ code: 'ae', file: 'ae.json' },
|
||||
],
|
||||
lazy: true,
|
||||
defaultLocale: 'en',
|
||||
strategy: 'no_prefix',
|
||||
langDir: 'locales/',
|
||||
},
|
||||
|
||||
vite: {
|
||||
optimizeDeps: { include: ['quill'] },
|
||||
},
|
||||
|
||||
router: {
|
||||
options: { linkExactActiveClass: 'active' },
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-09-21',
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: process.env.API_BASE_URL,
|
||||
},
|
||||
},
|
||||
|
||||
devtools: {
|
||||
timeline: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -22,7 +22,7 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<form class="space-y-5" @submit.prevent="submitForm()">
|
||||
<form v-if="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 Karakter</label>
|
||||
@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div :class="{ 'has-error': $validate.form.type.$error, 'has-success': isSubmitted && !$validate.form.type.$error }">
|
||||
<label for="type">Jenis</label>
|
||||
<multiselect id="format"
|
||||
<multiselect id="type"
|
||||
v-model="form.type"
|
||||
:options="typeOptions"
|
||||
class="custom-multiselect"
|
||||
@ -53,11 +53,11 @@
|
||||
<label for="grade">Jenis Kelamin</label>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<label class="inline-flex">
|
||||
<input type="radio" v-model="form.sex" class="form-radio" value="true" />
|
||||
<input type="radio" v-model="form.sex" class="form-radio" :value="true" />
|
||||
<span>Laki-laki</span>
|
||||
</label>
|
||||
<label class="inline-flex">
|
||||
<input type="radio" v-model="form.sex" class="form-radio" value="false" />
|
||||
<input type="radio" v-model="form.sex" class="form-radio" :value="false" />
|
||||
<span>Perempuan</span>
|
||||
</label>
|
||||
</div>
|
||||
@ -88,7 +88,7 @@
|
||||
<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>
|
||||
<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>
|
||||
@ -104,14 +104,14 @@
|
||||
<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>
|
||||
<p class="text-danger mt-1">Gambar ikon 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"><icon-save class="me-1" /> Simpan</button>
|
||||
<button type="reset" class="btn btn-dark !py-1 ml-1"><icon-restore class="me-1" />Reset</button>
|
||||
<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>
|
||||
@ -122,22 +122,34 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { integer, minValue,required } from '@vuelidate/validators';
|
||||
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 Konten' });
|
||||
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: {
|
||||
name: { required },
|
||||
description: { required },
|
||||
sex: { required },
|
||||
type: { required },
|
||||
featured_image: { required },
|
||||
featured_icon: { required },
|
||||
featured_image: { imageType, maxFileSize },
|
||||
featured_icon: { imageType, maxFileSize},
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
@ -145,27 +157,34 @@
|
||||
const config = useRuntimeConfig();
|
||||
const typeOptions = [{ "value": "manusia", "label": "Manusia" }, { "value": "hewan", "label": "Hewan" } ]
|
||||
|
||||
const { data: form } = await useAsyncData('characters',
|
||||
() => $fetch(`${config.public.apiBase}character/characters/${route.params.id}`, {})
|
||||
const originalData = ref<any>(null);
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const featuredImageUploader = ref(null);
|
||||
const featuredIconUploader = ref(null);
|
||||
|
||||
const { data: form, refresh } = await useAsyncData('characters',
|
||||
() => $fetch(`${config.public.apiBase}/character/characters/${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;
|
||||
|
||||
// single image upload
|
||||
new FileUploadWithPreview('featuredImage', {
|
||||
featuredImageUploader.value =new FileUploadWithPreview('featuredImage', {
|
||||
images: {
|
||||
baseImage: form.value.featured_image || '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
},
|
||||
});
|
||||
|
||||
// single image upload
|
||||
new FileUploadWithPreview('featuredIcon', {
|
||||
featuredIconUploader.value = new FileUploadWithPreview('featuredIcon', {
|
||||
images: {
|
||||
baseImage: form.value.featured_icon || '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
@ -173,6 +192,10 @@
|
||||
});
|
||||
|
||||
form.value.type = typeOptions.find(option => option.value === form.value.type);
|
||||
originalData.value = {
|
||||
...form.value,
|
||||
type: typeOptions.find(option => option.value === form.value.type),
|
||||
};
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
@ -189,14 +212,14 @@
|
||||
formData.append('type', form.value.type.value);
|
||||
formData.append('description', form.value.description);
|
||||
|
||||
if (form.value.featured_image) {
|
||||
if (form.value.featured_image && typeof form.value.featured_image !== 'string') {
|
||||
formData.append('featured_image', form.value.featured_image);
|
||||
}
|
||||
if (form.value.featured_icon) {
|
||||
if (form.value.featured_icon && typeof form.value.featured_icon !== 'string') {
|
||||
formData.append('featured_icon', form.value.featured_icon);
|
||||
}
|
||||
|
||||
await $fetch(`${config.public.apiBase}character/characters/${route.params.id}/`, {
|
||||
await $fetch(`${config.public.apiBase}/character/characters/${route.params.id}/`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
@ -230,10 +253,21 @@
|
||||
|
||||
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
|
||||
if (!allowed.includes(file.type)) {
|
||||
alert('Hanya PNG yang diizinkan')
|
||||
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>
|
||||
@ -50,7 +50,7 @@
|
||||
</template>
|
||||
</div>
|
||||
<div :class="{ 'has-error': $validate.form.sex.$error, 'has-success': isSubmitted && !$validate.form.sex.$error }">
|
||||
<label for="grade">Jenis Kelamin</label>
|
||||
<label for="sex">Jenis Kelamin</label>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<label class="inline-flex">
|
||||
<input type="radio" v-model="form.sex" class="form-radio" value="1" />
|
||||
@ -88,7 +88,7 @@
|
||||
<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>
|
||||
<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>
|
||||
@ -104,7 +104,7 @@
|
||||
<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>
|
||||
<p class="text-danger mt-1">Gambar ikon 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>
|
||||
@ -123,30 +123,40 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
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: 'Tambah Konten' });
|
||||
useHead({ title: 'Tambah Karakter' });
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
description: '',
|
||||
sex: null,
|
||||
type: '',
|
||||
type: null,
|
||||
featured_image: '',
|
||||
featured_icon: '',
|
||||
});
|
||||
const isSubmitted = ref(false);
|
||||
|
||||
const imageType = helpers.withMessage('Format gambar harus PNG atau JPG', (value) => {
|
||||
if (!value) return false;
|
||||
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 false;
|
||||
return value.size <= 1048576; // 1MB dalam byte
|
||||
});
|
||||
|
||||
const rules = {
|
||||
form: {
|
||||
name: { required },
|
||||
description: { required },
|
||||
sex: { required },
|
||||
type: { required },
|
||||
featured_image: { required },
|
||||
featured_icon: { required },
|
||||
featured_image: { required, imageType, maxFileSize },
|
||||
featured_icon: { required, imageType, maxFileSize },
|
||||
}
|
||||
};
|
||||
const $validate = useVuelidate(rules, { form });
|
||||
@ -200,7 +210,7 @@
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}character/characters/`, {
|
||||
await $fetch(`${config.public.apiBase}/character/characters/`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
@ -247,7 +257,7 @@
|
||||
name: '',
|
||||
description: '',
|
||||
sex: null,
|
||||
type: '',
|
||||
type: null,
|
||||
featured_image: '',
|
||||
featured_icon: '',
|
||||
};
|
||||
|
||||
@ -130,7 +130,7 @@
|
||||
const params = reactive({
|
||||
search: null,
|
||||
current_page: 1,
|
||||
pagesize: 10,
|
||||
pagesize: 50,
|
||||
sort_column: 'name',
|
||||
sort_direction: 'asc',
|
||||
});
|
||||
@ -141,8 +141,6 @@
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: params.pagesize,
|
||||
ordering: (params.sort_direction == 'desc' ? '-' : '') + params.sort_column,
|
||||
search: params.search
|
||||
}})
|
||||
}, {
|
||||
watch: [params]
|
||||
|
||||
@ -207,7 +207,7 @@
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const { data: formats } = await useAsyncData('formats',
|
||||
() => $fetch(`${config.public.apiBase}content/formats/`)
|
||||
() => $fetch(`${config.public.apiBase}/content/formats/`)
|
||||
);
|
||||
|
||||
const { data: form } = await useAsyncData('contents',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user