karakter
This commit is contained in:
parent
9293c4cde5
commit
f49a7e9fc7
@ -26,12 +26,14 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
css: ['~/assets/css/app.css'],
|
css: ['~/assets/css/app.css'],
|
||||||
|
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: ['@pinia/nuxt', '@nuxtjs/i18n'],
|
modules: ['@pinia/nuxt', '@nuxtjs/i18n'],
|
||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
@ -58,12 +60,15 @@ export default defineNuxtConfig({
|
|||||||
strategy: 'no_prefix',
|
strategy: 'no_prefix',
|
||||||
langDir: 'locales/',
|
langDir: 'locales/',
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
optimizeDeps: { include: ['quill'] },
|
optimizeDeps: { include: ['quill'] },
|
||||||
},
|
},
|
||||||
|
|
||||||
router: {
|
router: {
|
||||||
options: { linkExactActiveClass: 'active' },
|
options: { linkExactActiveClass: 'active' },
|
||||||
},
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-09-21',
|
compatibilityDate: '2024-09-21',
|
||||||
|
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
@ -71,4 +76,10 @@ export default defineNuxtConfig({
|
|||||||
apiBase: process.env.API_BASE_URL,
|
apiBase: process.env.API_BASE_URL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
devtools: {
|
||||||
|
timeline: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
@ -22,7 +22,7 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5">
|
<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="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 }">
|
<div :class="{ 'has-error': $validate.form.name.$error, 'has-success': isSubmitted && !$validate.form.name.$error }">
|
||||||
<label for="name">Nama Karakter</label>
|
<label for="name">Nama Karakter</label>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div :class="{ 'has-error': $validate.form.type.$error, 'has-success': isSubmitted && !$validate.form.type.$error }">
|
<div :class="{ 'has-error': $validate.form.type.$error, 'has-success': isSubmitted && !$validate.form.type.$error }">
|
||||||
<label for="type">Jenis</label>
|
<label for="type">Jenis</label>
|
||||||
<multiselect id="format"
|
<multiselect id="type"
|
||||||
v-model="form.type"
|
v-model="form.type"
|
||||||
:options="typeOptions"
|
:options="typeOptions"
|
||||||
class="custom-multiselect"
|
class="custom-multiselect"
|
||||||
@ -53,11 +53,11 @@
|
|||||||
<label for="grade">Jenis Kelamin</label>
|
<label for="grade">Jenis Kelamin</label>
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
<label class="inline-flex">
|
<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>
|
<span>Laki-laki</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="inline-flex">
|
<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>
|
<span>Perempuan</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -88,7 +88,7 @@
|
|||||||
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
||||||
</label>
|
</label>
|
||||||
<template v-if="isSubmitted && $validate.form.featured_image.$error">
|
<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>
|
</template>
|
||||||
<div class="custom-file-container__image-preview"></div>
|
<div class="custom-file-container__image-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -104,14 +104,14 @@
|
|||||||
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
||||||
</label>
|
</label>
|
||||||
<template v-if="isSubmitted && $validate.form.featured_icon.$error">
|
<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>
|
</template>
|
||||||
<div class="custom-file-container__image-preview"></div>
|
<div class="custom-file-container__image-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center ltr:ml-auto mt-8">
|
<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="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"><icon-restore class="me-1" />Reset</button>
|
<button type="reset" class="btn btn-dark !py-1 ml-1" @click="resetForm"><icon-restore class="me-1" />Reset</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -122,22 +122,34 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
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 Multiselect from '@suadelabs/vue3-multiselect';
|
||||||
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
||||||
import '@/assets/css/file-upload-preview.css';
|
import '@/assets/css/file-upload-preview.css';
|
||||||
|
|
||||||
useHead({ title: 'Edit Konten' });
|
useHead({ title: 'Edit Karakter' });
|
||||||
|
|
||||||
const isSubmitted = ref(false);
|
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 = {
|
const rules = {
|
||||||
form: {
|
form: {
|
||||||
name: { required },
|
name: { required },
|
||||||
description: { required },
|
description: { required },
|
||||||
sex: { required },
|
sex: { required },
|
||||||
type: { required },
|
type: { required },
|
||||||
featured_image: { required },
|
featured_image: { imageType, maxFileSize },
|
||||||
featured_icon: { required },
|
featured_icon: { imageType, maxFileSize},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -145,27 +157,34 @@
|
|||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const typeOptions = [{ "value": "manusia", "label": "Manusia" }, { "value": "hewan", "label": "Hewan" } ]
|
const typeOptions = [{ "value": "manusia", "label": "Manusia" }, { "value": "hewan", "label": "Hewan" } ]
|
||||||
|
|
||||||
const { data: form } = await useAsyncData('characters',
|
const originalData = ref<any>(null);
|
||||||
() => $fetch(`${config.public.apiBase}character/characters/${route.params.id}`, {})
|
|
||||||
|
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 });
|
const $validate = useVuelidate(rules, { form });
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (!form.value) return;
|
||||||
|
|
||||||
const fileupload = await import('file-upload-with-preview');
|
const fileupload = await import('file-upload-with-preview');
|
||||||
let FileUploadWithPreview = fileupload.default;
|
let FileUploadWithPreview = fileupload.default;
|
||||||
|
|
||||||
// single image upload
|
featuredImageUploader.value =new FileUploadWithPreview('featuredImage', {
|
||||||
new FileUploadWithPreview('featuredImage', {
|
|
||||||
images: {
|
images: {
|
||||||
baseImage: form.value.featured_image || '/assets/images/file-preview.svg',
|
baseImage: form.value.featured_image || '/assets/images/file-preview.svg',
|
||||||
backgroundImage: '',
|
backgroundImage: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// single image upload
|
featuredIconUploader.value = new FileUploadWithPreview('featuredIcon', {
|
||||||
new FileUploadWithPreview('featuredIcon', {
|
|
||||||
images: {
|
images: {
|
||||||
baseImage: form.value.featured_icon || '/assets/images/file-preview.svg',
|
baseImage: form.value.featured_icon || '/assets/images/file-preview.svg',
|
||||||
backgroundImage: '',
|
backgroundImage: '',
|
||||||
@ -173,6 +192,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
form.value.type = typeOptions.find(option => option.value === form.value.type);
|
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 () => {
|
const submitForm = async () => {
|
||||||
@ -189,14 +212,14 @@
|
|||||||
formData.append('type', form.value.type.value);
|
formData.append('type', form.value.type.value);
|
||||||
formData.append('description', form.value.description);
|
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);
|
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);
|
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',
|
method: 'PUT',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -230,10 +253,21 @@
|
|||||||
|
|
||||||
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
|
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
|
||||||
if (!allowed.includes(file.type)) {
|
if (!allowed.includes(file.type)) {
|
||||||
alert('Hanya PNG yang diizinkan')
|
alert('Hanya PNG atau JPG yang diizinkan')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form.value.featured_icon = file
|
form.value.featured_icon = file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
isSubmitted.value = false;
|
||||||
|
$validate.value.form.$reset();
|
||||||
|
|
||||||
|
featuredImageUploader?.value?.clearPreviewPanel();
|
||||||
|
featuredIconUploader?.value?.clearPreviewPanel();
|
||||||
|
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div :class="{ 'has-error': $validate.form.sex.$error, 'has-success': isSubmitted && !$validate.form.sex.$error }">
|
<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">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
<label class="inline-flex">
|
<label class="inline-flex">
|
||||||
<input type="radio" v-model="form.sex" class="form-radio" value="1" />
|
<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>
|
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
||||||
</label>
|
</label>
|
||||||
<template v-if="isSubmitted && $validate.form.featured_image.$error">
|
<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>
|
</template>
|
||||||
<div class="custom-file-container__image-preview"></div>
|
<div class="custom-file-container__image-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -104,7 +104,7 @@
|
|||||||
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
<span class="custom-file-container__custom-file__custom-file-control ltr:pr-20 rtl:pl-20"></span>
|
||||||
</label>
|
</label>
|
||||||
<template v-if="isSubmitted && $validate.form.featured_icon.$error">
|
<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>
|
</template>
|
||||||
<div class="custom-file-container__image-preview"></div>
|
<div class="custom-file-container__image-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -123,30 +123,40 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
import { useVuelidate } from '@vuelidate/core';
|
import { useVuelidate } from '@vuelidate/core';
|
||||||
import { required } from '@vuelidate/validators';
|
import { required, helpers } from '@vuelidate/validators';
|
||||||
import Multiselect from '@suadelabs/vue3-multiselect';
|
import Multiselect from '@suadelabs/vue3-multiselect';
|
||||||
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
||||||
import '@/assets/css/file-upload-preview.css';
|
import '@/assets/css/file-upload-preview.css';
|
||||||
|
|
||||||
useHead({ title: 'Tambah Konten' });
|
useHead({ title: 'Tambah Karakter' });
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
sex: null,
|
sex: null,
|
||||||
type: '',
|
type: null,
|
||||||
featured_image: '',
|
featured_image: '',
|
||||||
featured_icon: '',
|
featured_icon: '',
|
||||||
});
|
});
|
||||||
const isSubmitted = ref(false);
|
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 = {
|
const rules = {
|
||||||
form: {
|
form: {
|
||||||
name: { required },
|
name: { required },
|
||||||
description: { required },
|
description: { required },
|
||||||
sex: { required },
|
sex: { required },
|
||||||
type: { required },
|
type: { required },
|
||||||
featured_image: { required },
|
featured_image: { required, imageType, maxFileSize },
|
||||||
featured_icon: { required },
|
featured_icon: { required, imageType, maxFileSize },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const $validate = useVuelidate(rules, { form });
|
const $validate = useVuelidate(rules, { form });
|
||||||
@ -200,7 +210,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
await $fetch(`${config.public.apiBase}character/characters/`, {
|
await $fetch(`${config.public.apiBase}/character/characters/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@ -247,7 +257,7 @@
|
|||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
sex: null,
|
sex: null,
|
||||||
type: '',
|
type: null,
|
||||||
featured_image: '',
|
featured_image: '',
|
||||||
featured_icon: '',
|
featured_icon: '',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -130,7 +130,7 @@
|
|||||||
const params = reactive({
|
const params = reactive({
|
||||||
search: null,
|
search: null,
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
pagesize: 10,
|
pagesize: 50,
|
||||||
sort_column: 'name',
|
sort_column: 'name',
|
||||||
sort_direction: 'asc',
|
sort_direction: 'asc',
|
||||||
});
|
});
|
||||||
@ -141,8 +141,6 @@
|
|||||||
params: {
|
params: {
|
||||||
page: params.current_page,
|
page: params.current_page,
|
||||||
page_size: params.pagesize,
|
page_size: params.pagesize,
|
||||||
ordering: (params.sort_direction == 'desc' ? '-' : '') + params.sort_column,
|
|
||||||
search: params.search
|
|
||||||
}})
|
}})
|
||||||
}, {
|
}, {
|
||||||
watch: [params]
|
watch: [params]
|
||||||
|
|||||||
@ -207,7 +207,7 @@
|
|||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
const { data: formats } = await useAsyncData('formats',
|
const { data: formats } = await useAsyncData('formats',
|
||||||
() => $fetch(`${config.public.apiBase}content/formats/`)
|
() => $fetch(`${config.public.apiBase}/content/formats/`)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: form } = await useAsyncData('contents',
|
const { data: form } = await useAsyncData('contents',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user