merapikan koding
This commit is contained in:
parent
f49a7e9fc7
commit
ba5de3c7a7
92
assets/css/quill-editor.css
Normal file
92
assets/css/quill-editor.css
Normal file
@ -0,0 +1,92 @@
|
||||
.ql-snow .ql-editor img {
|
||||
margin: 20px;
|
||||
height: 176px;
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
.ltr .ql-snow .ql-editor img {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.rtl .ql-snow .ql-editor img {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.dark .ql-toolbar.ql-snow,
|
||||
.dark .ql-container.ql-snow {
|
||||
border-color: #17263c;
|
||||
}
|
||||
|
||||
.dark .ql-container.ql-snow {
|
||||
background-color: #121e32;
|
||||
}
|
||||
|
||||
.ql-toolbar.ql-snow {
|
||||
box-sizing: border-box;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
border-width: 1px;
|
||||
border-color: #e0e6ed !important;
|
||||
padding: 8px;
|
||||
font-family: Nunito, sans-serif;
|
||||
}
|
||||
|
||||
.dark .ql-toolbar.ql-snow,
|
||||
.dark .ql-container.ql-snow {
|
||||
border-color: #17263c !important;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border-bottom-right-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-width: 1px;
|
||||
border-top: 0px !important;
|
||||
border-color: #e0e6ed !important;
|
||||
}
|
||||
|
||||
.ql-snow .ql-editor {
|
||||
max-height: 200px;
|
||||
min-height: 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.rtl .ql-snow .ql-editor {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dark .ql-snow .ql-stroke {
|
||||
stroke: #888ea8;
|
||||
}
|
||||
|
||||
.dark .ql-snow .ql-picker,
|
||||
.dark .ql-snow .ql-editor h1,
|
||||
.dark .ql-snow .ql-editor p {
|
||||
color: #888ea8;
|
||||
}
|
||||
|
||||
.rtl .ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
|
||||
right: auto !important;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.dark .ql-snow .ql-tooltip {
|
||||
background-color: #060818;
|
||||
border-color: #17263c;
|
||||
color: #888ea8;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip input[type='text'] {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dark .ql-snow .ql-tooltip input[type='text'] {
|
||||
background-color: #121e32;
|
||||
border-color: #17263c;
|
||||
color: #888ea8;
|
||||
}
|
||||
|
||||
.rtl .ql-toolbar.ql-snow .ql-formats {
|
||||
margin-right: 0px !important;
|
||||
margin-left: 15px;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="dark:text-white-dark text-center ltr:sm:text-left rtl:sm:text-right p-6 pt-0 mt-auto">
|
||||
© {{ new Date().getFullYear() }}. Vristo All rights reserved.
|
||||
© {{ new Date().getFullYear() }}. Freekake All rights reserved.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
<div class="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-[#0e1726]">
|
||||
<div class="horizontal-logo flex items-center justify-between ltr:mr-2 rtl:ml-2 lg:hidden">
|
||||
<NuxtLink to="/" class="main-logo flex shrink-0 items-center">
|
||||
<img class="inline w-8 ltr:-ml-1 rtl:-mr-1" src="/assets/images/logo.svg" alt="" />
|
||||
<img class="inline w-8 ltr:-ml-1 rtl:-mr-1" src="/assets/images/freekake.png" alt="" />
|
||||
<span
|
||||
class="hidden align-middle text-2xl font-semibold transition-all duration-300 ltr:ml-1.5 rtl:mr-1.5 dark:text-white-light md:inline"
|
||||
>VRISTO</span
|
||||
>ADMIN</span
|
||||
>
|
||||
</NuxtLink>
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<icon-menu class="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden ltr:mr-2 rtl:ml-2 sm:block">
|
||||
<!-- <div class="hidden ltr:mr-2 rtl:ml-2 sm:block">
|
||||
<ul class="flex items-center space-x-2 rtl:space-x-reverse dark:text-[#d0d2d6]">
|
||||
<li>
|
||||
<NuxtLink
|
||||
@ -46,12 +46,12 @@
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> -->
|
||||
<div
|
||||
class="flex items-center space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] sm:flex-1 ltr:sm:ml-0 sm:rtl:mr-0 lg:space-x-2"
|
||||
>
|
||||
<div class="sm:ltr:mr-auto sm:rtl:ml-auto">
|
||||
<form
|
||||
<!-- <form
|
||||
class="absolute inset-x-0 top-1/2 z-10 mx-4 hidden -translate-y-1/2 sm:relative sm:top-0 sm:mx-0 sm:block sm:translate-y-0"
|
||||
:class="{ '!block': search }"
|
||||
@submit.prevent="search = false"
|
||||
@ -81,7 +81,7 @@
|
||||
@click="search = !search"
|
||||
>
|
||||
<icon-search class="mx-auto h-4.5 w-4.5 dark:text-[#d0d2d6]" />
|
||||
</button>
|
||||
</button> -->
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
@ -110,7 +110,7 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="dropdown shrink-0">
|
||||
<!-- <div class="dropdown shrink-0">
|
||||
<client-only>
|
||||
<Popper :placement="store.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'" offsetDistance="8">
|
||||
<button
|
||||
@ -145,9 +145,9 @@
|
||||
</template>
|
||||
</Popper>
|
||||
</client-only>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="dropdown shrink-0">
|
||||
<!-- <div class="dropdown shrink-0">
|
||||
<client-only>
|
||||
<Popper :placement="store.rtlClass === 'rtl' ? 'bottom-start' : 'bottom-end'" offsetDistance="8">
|
||||
<button
|
||||
@ -210,9 +210,9 @@
|
||||
</template>
|
||||
</Popper>
|
||||
</client-only>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="dropdown shrink-0">
|
||||
<!-- <div class="dropdown shrink-0">
|
||||
<client-only>
|
||||
<Popper :placement="store.rtlClass === 'rtl' ? 'bottom-end' : 'bottom-start'" offsetDistance="8">
|
||||
<button
|
||||
@ -288,7 +288,7 @@
|
||||
</template>
|
||||
</Popper>
|
||||
</client-only>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="dropdown shrink-0">
|
||||
<client-only>
|
||||
@ -355,520 +355,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- horizontal menu -->
|
||||
<ul
|
||||
class="horizontal-menu hidden border-t border-[#ebedf2] bg-white px-6 py-1.5 font-semibold text-black rtl:space-x-reverse dark:border-[#191e3a] dark:bg-[#0e1726] dark:text-white-dark lg:space-x-1.5 xl:space-x-8"
|
||||
>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-dashboard class="shrink-0" />
|
||||
|
||||
<span class="px-2">{{ $t('dashboard') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/">{{ $t('sales') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/analytics">{{ $t('analytics') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/finance">{{ $t('finance') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/crypto">{{ $t('crypto') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-apps class="shrink-0" />
|
||||
|
||||
<span class="px-2">{{ $t('apps') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/apps/chat">{{ $t('chat') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/mailbox">{{ $t('mailbox') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/todolist">{{ $t('todo_list') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/notes">{{ $t('notes') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/scrumboard">{{ $t('scrumboard') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/contacts">{{ $t('contacts') }}</NuxtLink>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('invoice') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/list">{{ $t('list') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/preview">{{ $t('preview') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/add">{{ $t('add') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/edit">{{ $t('edit') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/calendar">{{ $t('calendar') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-components class="shrink-0" />
|
||||
<span class="px-2">{{ $t('components') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/components/tabs">{{ $t('tabs') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/accordions">{{ $t('accordions') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/modals">{{ $t('modals') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/cards">{{ $t('cards') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/carousel">{{ $t('carousel') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/countdown">{{ $t('countdown') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/counter">{{ $t('counter') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/sweetalert">{{ $t('sweet_alerts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/timeline">{{ $t('timeline') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/notifications">{{ $t('notifications') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/media-object">{{ $t('media_object') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/list-group">{{ $t('list_group') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/pricing-table">{{ $t('pricing_tables') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/lightbox">{{ $t('lightbox') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-elements class="shrink-0" />
|
||||
<span class="px-2">{{ $t('elements') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/elements/alerts">{{ $t('alerts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/avatar">{{ $t('avatar') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/badges">{{ $t('badges') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/breadcrumbs">{{ $t('breadcrumbs') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/buttons">{{ $t('buttons') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/buttons-group">{{ $t('button_groups') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/color-library">{{ $t('color_library') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/dropdown">{{ $t('dropdown') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/infobox">{{ $t('infobox') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/jumbotron">{{ $t('jumbotron') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/loader">{{ $t('loader') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/pagination">{{ $t('pagination') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/popovers">{{ $t('popovers') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/progress-bar">{{ $t('progress_bar') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/search">{{ $t('search') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/tooltips">{{ $t('tooltips') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/treeview">{{ $t('treeview') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/typography">{{ $t('typography') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-datatables class="shrink-0" />
|
||||
<span class="px-2">{{ $t('tables') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/tables">{{ $t('tables') }}</NuxtLink>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('datatables') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/basic">{{ $t('basic') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/advanced">{{ $t('advanced') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/skin">{{ $t('skin') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/order-sorting">{{ $t('order_sorting') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/columns-filter">{{ $t('columns_filter') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/multi-column">{{ $t('multi_column') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/multiple-tables">{{ $t('multiple_tables') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/alt-pagination">{{ $t('alt_pagination') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/checkbox">{{ $t('checkbox') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/range-search">{{ $t('range_search') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/export">{{ $t('export') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/sticky-header">{{ $t('sticky_header') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/clone-header">{{ $t('clone_header') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/column-chooser">{{ $t('column_chooser') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-forms class="shrink-0" />
|
||||
<span class="px-2">{{ $t('forms') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/forms/basic">{{ $t('basic') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/input-group">{{ $t('input_group') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/layouts">{{ $t('layouts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/validation">{{ $t('validation') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/input-mask">{{ $t('input_mask') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/select2">{{ $t('select2') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/touchspin">{{ $t('touchspin') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/checkbox-radio">{{ $t('checkbox_and_radio') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/switches">{{ $t('switches') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/wizards">{{ $t('wizards') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/file-upload">{{ $t('file_upload') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/quill-editor">{{ $t('quill_editor') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/markdown-editor">{{ $t('markdown_editor') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/date-picker">{{ $t('date_and_range_picker') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/clipboard">{{ $t('clipboard') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-pages class="shrink-0" />
|
||||
<span class="px-2">{{ $t('pages') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('users') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/users/profile">{{ $t('profile') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/users/user-account-settings">{{ $t('account_settings') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/knowledge-base">{{ $t('knowledge_base') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/contact-us-boxed" target="_blank">{{ $t('contact_us_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/contact-us-cover" target="_blank">{{ $t('contact_us_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/faq">FAQ</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/coming-soon-boxed" target="_blank">{{ $t('coming_soon_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/coming-soon-cover" target="_blank">{{ $t('coming_soon_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/maintenence" target="_blank">{{ $t('maintenence') }}</NuxtLink>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('error') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/pages/error404" target="_blank">{{ $t('404') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/error500" target="_blank">{{ $t('500') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/error503" target="_blank">{{ $t('503') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('login') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/auth/cover-login" target="_blank">{{ $t('login_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/auth/boxed-signin" target="_blank">{{ $t('login_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('register') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/auth/cover-register" target="_blank">{{ $t('register_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/auth/boxed-signup" target="_blank">{{ $t('register_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('password_recovery') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/auth/cover-password-reset" target="_blank">{{ $t('recover_id_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/auth/boxed-password-reset" target="_blank">{{ $t('recover_id_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<a href="javascript:;"
|
||||
>{{ $t('lockscreen') }}
|
||||
<div class="-rotate-90 ltr:ml-auto rtl:mr-auto rtl:rotate-90">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul
|
||||
class="absolute top-0 z-[10] hidden min-w-[180px] rounded bg-white p-0 py-2 text-dark shadow ltr:left-[95%] rtl:right-[95%] dark:bg-[#1b2e4b] dark:text-white-dark"
|
||||
>
|
||||
<li>
|
||||
<NuxtLink to="/auth/cover-lockscreen" target="_blank">{{ $t('unlock_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/auth/boxed-lockscreen" target="_blank">{{ $t('unlock_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu nav-item relative">
|
||||
<a href="javascript:;" class="nav-link">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-more class="shrink-0" />
|
||||
|
||||
<span class="px-2">{{ $t('more') }}</span>
|
||||
</div>
|
||||
<div class="right_arrow">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="sub-menu">
|
||||
<li>
|
||||
<NuxtLink to="/dragndrop">{{ $t('drag_and_drop') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/charts">{{ $t('charts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/font-icons">{{ $t('font_icons') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/widgets">{{ $t('widgets') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vristo.sbthemes.com" target="_blank">{{ $t('documentation') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@ -1,742 +0,0 @@
|
||||
<template>
|
||||
<div :class="{ 'dark text-white-dark': store.semidark }">
|
||||
<nav class="sidebar fixed bottom-0 top-0 z-50 h-full min-h-screen w-[260px] shadow-[5px_0_25px_0_rgba(94,92,154,0.1)] transition-all duration-300">
|
||||
<div class="h-full bg-white dark:bg-[#0e1726]">
|
||||
<div class="flex items-center justify-between px-4 py-3">
|
||||
<NuxtLink to="/" class="main-logo flex shrink-0 items-center">
|
||||
<img class="ml-[5px] w-8 flex-none" src="/assets/images/logo.svg" alt="" />
|
||||
<span class="align-middle text-2xl font-semibold ltr:ml-1.5 rtl:mr-1.5 dark:text-white-light lg:inline">VRISTO</span>
|
||||
</NuxtLink>
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="collapse-icon flex h-8 w-8 items-center rounded-full transition duration-300 hover:bg-gray-500/10 hover:text-primary rtl:rotate-180 dark:text-white-light dark:hover:bg-dark-light/10"
|
||||
@click="store.toggleSidebar()"
|
||||
>
|
||||
<icon-carets-down class="m-auto rotate-90" />
|
||||
</a>
|
||||
</div>
|
||||
<client-only>
|
||||
<perfect-scrollbar
|
||||
:options="{
|
||||
swipeEasing: true,
|
||||
wheelPropagation: false,
|
||||
}"
|
||||
class="relative h-[calc(100vh-80px)]"
|
||||
>
|
||||
<ul class="relative space-y-0.5 p-4 py-0 font-semibold">
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'dashboard' }"
|
||||
@click="activeDropdown === 'dashboard' ? (activeDropdown = null) : (activeDropdown = 'dashboard')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-dashboard class="shrink-0 group-hover:!text-primary" />
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">
|
||||
{{ $t('dashboard') }}
|
||||
</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'dashboard' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'dashboard'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/" @click="toggleMobileMenu">{{ $t('sales') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/analytics" @click="toggleMobileMenu">{{ $t('analytics') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/finance" @click="toggleMobileMenu">{{ $t('finance') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/crypto" @click="toggleMobileMenu">{{ $t('crypto') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<h2 class="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
|
||||
<icon-minus class="hidden h-5 w-4 flex-none" />
|
||||
<span>{{ $t('apps') }}</span>
|
||||
</h2>
|
||||
|
||||
<li class="nav-item">
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/chat" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-chat class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('chat')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/mailbox" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-mailbox class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('mailbox')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/todolist" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-todo class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('todo_list')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/notes" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-notes class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('notes')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/scrumboard" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-scrumboard class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('scrumboard')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/contacts" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-contacts class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('contacts')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'invoice' }"
|
||||
@click="activeDropdown === 'invoice' ? (activeDropdown = null) : (activeDropdown = 'invoice')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-invoice class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('invoice')
|
||||
}}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'invoice' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'invoice'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/list" @click="toggleMobileMenu">{{ $t('list') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/preview" @click="toggleMobileMenu">{{ $t('preview') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/add" @click="toggleMobileMenu">{{ $t('add') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/apps/invoice/edit" @click="toggleMobileMenu">{{ $t('edit') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<NuxtLink to="/apps/calendar" class="group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-calendar class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('calendar')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<h2 class="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
|
||||
<icon-minus class="hidden h-5 w-4 flex-none" />
|
||||
<span>{{ $t('user_interface') }}</span>
|
||||
</h2>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'components' }"
|
||||
@click="activeDropdown === 'components' ? (activeDropdown = null) : (activeDropdown = 'components')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-components class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('components')
|
||||
}}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'components' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'components'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/components/tabs" @click="toggleMobileMenu">{{ $t('tabs') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/accordions" @click="toggleMobileMenu">{{ $t('accordions') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/modals" @click="toggleMobileMenu">{{ $t('modals') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/cards" @click="toggleMobileMenu">{{ $t('cards') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/carousel" @click="toggleMobileMenu">{{ $t('carousel') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/countdown" @click="toggleMobileMenu">{{ $t('countdown') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/counter" @click="toggleMobileMenu">{{ $t('counter') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/sweetalert" @click="toggleMobileMenu">{{ $t('sweet_alerts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/timeline" @click="toggleMobileMenu">{{ $t('timeline') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/notifications" @click="toggleMobileMenu">{{ $t('notifications') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/media-object" @click="toggleMobileMenu">{{ $t('media_object') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/list-group" @click="toggleMobileMenu">{{ $t('list_group') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/pricing-table" @click="toggleMobileMenu">{{ $t('pricing_tables') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/components/lightbox" @click="toggleMobileMenu">{{ $t('lightbox') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'elements' }"
|
||||
@click="activeDropdown === 'elements' ? (activeDropdown = null) : (activeDropdown = 'elements')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-elements class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('elements')
|
||||
}}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'elements' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'elements'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/elements/alerts" @click="toggleMobileMenu">{{ $t('alerts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/avatar" @click="toggleMobileMenu">{{ $t('avatar') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/badges" @click="toggleMobileMenu">{{ $t('badges') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/breadcrumbs" @click="toggleMobileMenu">{{ $t('breadcrumbs') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/buttons" @click="toggleMobileMenu">{{ $t('buttons') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/buttons-group" @click="toggleMobileMenu">{{ $t('button_groups') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/color-library" @click="toggleMobileMenu">{{ $t('color_library') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/dropdown" @click="toggleMobileMenu">{{ $t('dropdown') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/infobox" @click="toggleMobileMenu">{{ $t('infobox') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/jumbotron" @click="toggleMobileMenu">{{ $t('jumbotron') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/loader" @click="toggleMobileMenu">{{ $t('loader') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/pagination" @click="toggleMobileMenu">{{ $t('pagination') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/popovers" @click="toggleMobileMenu">{{ $t('popovers') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/progress-bar" @click="toggleMobileMenu">{{ $t('progress_bar') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/search" @click="toggleMobileMenu">{{ $t('search') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/tooltips" @click="toggleMobileMenu">{{ $t('tooltips') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/treeview" @click="toggleMobileMenu">{{ $t('treeview') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/elements/typography" @click="toggleMobileMenu">{{ $t('typography') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<NuxtLink to="/charts" class="nav-link group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-charts class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('charts')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<NuxtLink to="/widgets" class="nav-link group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-widgets class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('widgets')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<NuxtLink to="/font-icons" class="nav-link group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-font-icons class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('font_icons')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<NuxtLink to="/dragndrop" class="nav-link group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-drag-and-drop class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('drag_and_drop')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<h2 class="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
|
||||
<icon-minus class="hidden h-5 w-4 flex-none" />
|
||||
<span>{{ $t('tables_and_forms') }}</span>
|
||||
</h2>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<NuxtLink to="/tables" class="nav-link group" @click="toggleMobileMenu">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-tables class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('tables')
|
||||
}}</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'datatables' }"
|
||||
@click="activeDropdown === 'datatables' ? (activeDropdown = null) : (activeDropdown = 'datatables')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-datatables class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('datatables')
|
||||
}}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'datatables' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'datatables'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/datatables/basic" @click="toggleMobileMenu">{{ $t('basic') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/advanced" @click="toggleMobileMenu">{{ $t('advanced') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/skin" @click="toggleMobileMenu">{{ $t('skin') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/order-sorting" @click="toggleMobileMenu">{{ $t('order_sorting') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/columns-filter" @click="toggleMobileMenu">{{ $t('columns_filter') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/multi-column" @click="toggleMobileMenu">{{ $t('multi_column') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/multiple-tables" @click="toggleMobileMenu">{{ $t('multiple_tables') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/alt-pagination" @click="toggleMobileMenu">{{ $t('alt_pagination') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/checkbox" @click="toggleMobileMenu">{{ $t('checkbox') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/range-search" @click="toggleMobileMenu">{{ $t('range_search') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/export" @click="toggleMobileMenu">{{ $t('export') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/sticky-header" @click="toggleMobileMenu">{{ $t('sticky_header') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/clone-header" @click="toggleMobileMenu">{{ $t('clone_header') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/datatables/column-chooser" @click="toggleMobileMenu">{{ $t('column_chooser') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'forms' }"
|
||||
@click="activeDropdown === 'forms' ? (activeDropdown = null) : (activeDropdown = 'forms')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-forms class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('forms') }}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'forms' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'forms'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/forms/basic" @click="toggleMobileMenu">{{ $t('basic') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/input-group" @click="toggleMobileMenu">{{ $t('input_group') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/layouts" @click="toggleMobileMenu">{{ $t('layouts') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/validation" @click="toggleMobileMenu">{{ $t('validation') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/input-mask" @click="toggleMobileMenu">{{ $t('input_mask') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/select2" @click="toggleMobileMenu">{{ $t('select2') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/touchspin" @click="toggleMobileMenu">{{ $t('touchspin') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/checkbox-radio" @click="toggleMobileMenu">{{ $t('checkbox_and_radio') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/switches" @click="toggleMobileMenu">{{ $t('switches') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/wizards" @click="toggleMobileMenu">{{ $t('wizards') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/file-upload" @click="toggleMobileMenu">{{ $t('file_upload') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/quill-editor" @click="toggleMobileMenu">{{ $t('quill_editor') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/markdown-editor" @click="toggleMobileMenu">{{ $t('markdown_editor') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/date-picker" @click="toggleMobileMenu">{{ $t('date_and_range_picker') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/forms/clipboard" @click="toggleMobileMenu">{{ $t('clipboard') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<h2 class="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
|
||||
<icon-minus class="hidden h-5 w-4 flex-none" />
|
||||
<span>{{ $t('user_and_pages') }}</span>
|
||||
</h2>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'users' }"
|
||||
@click="activeDropdown === 'users' ? (activeDropdown = null) : (activeDropdown = 'users')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-users class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('users') }}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'users' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'users'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/users/profile" @click="toggleMobileMenu">{{ $t('profile') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/users/user-account-settings" @click="toggleMobileMenu">{{ $t('account_settings') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'pages' }"
|
||||
@click="activeDropdown === 'pages' ? (activeDropdown = null) : (activeDropdown = 'pages')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-pages class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{ $t('pages') }}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'pages' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'pages'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li>
|
||||
<NuxtLink to="/pages/knowledge-base" @click="toggleMobileMenu">{{ $t('knowledge_base') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/contact-us-boxed" target="_blank">{{ $t('contact_us_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/contact-us-cover" target="_blank">{{ $t('contact_us_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/faq" @click="toggleMobileMenu">{{ $t('faq') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/coming-soon-boxed" target="_blank">{{ $t('coming_soon_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/coming-soon-cover" target="_blank">{{ $t('coming_soon_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full before:h-[5px] before:w-[5px] before:rounded before:bg-gray-300 hover:bg-gray-100 ltr:before:mr-2 rtl:before:ml-2 dark:text-[#888ea8] dark:hover:bg-gray-900"
|
||||
@click="subActive === 'error' ? (subActive = null) : (subActive = 'error')"
|
||||
>
|
||||
{{ $t('error') }}
|
||||
<div class="ltr:ml-auto rtl:mr-auto" :class="{ '-rotate-90 rtl:rotate-90': subActive !== 'error' }">
|
||||
<icon-carets-down :fill="true" class="h-4 w-4" />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<vue-collapsible :isOpen="subActive === 'error'">
|
||||
<ul :unmount="false" class="sub-menu text-gray-500">
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/error404" target="_blank">{{ $t('404') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/error500" target="_blank">{{ $t('500') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/pages/error503" target="_blank">{{ $t('503') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/pages/maintenence" target="_blank">{{ $t('maintenence') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<button
|
||||
type="button"
|
||||
class="nav-link group w-full"
|
||||
:class="{ active: activeDropdown === 'authentication' }"
|
||||
@click="activeDropdown === 'authentication' ? (activeDropdown = null) : (activeDropdown = 'authentication')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<icon-menu-authentication class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('authentication')
|
||||
}}</span>
|
||||
</div>
|
||||
<div :class="{ '-rotate-90 rtl:rotate-90': activeDropdown !== 'authentication' }">
|
||||
<icon-caret-down />
|
||||
</div>
|
||||
</button>
|
||||
<vue-collapsible :isOpen="activeDropdown === 'authentication'">
|
||||
<ul class="sub-menu text-gray-500">
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/boxed-signin" target="_blank">{{ $t('login_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/boxed-signup" target="_blank">{{ $t('register_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/boxed-lockscreen" target="_blank">{{ $t('unlock_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/boxed-password-reset" target="_blank">{{ $t('recover_id_boxed') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/cover-login" target="_blank">{{ $t('login_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/cover-register" target="_blank">{{ $t('register_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/cover-lockscreen" target="_blank">{{ $t('unlock_cover') }}</NuxtLink>
|
||||
</li>
|
||||
<li @click="toggleMobileMenu">
|
||||
<NuxtLink to="/auth/cover-password-reset" target="_blank">{{ $t('recover_id_cover') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
<h2 class="-mx-4 mb-1 flex items-center bg-white-light/30 px-7 py-3 font-extrabold uppercase dark:bg-dark dark:bg-opacity-[0.08]">
|
||||
<icon-minus class="hidden h-5 w-4 flex-none" />
|
||||
<span>{{ $t('supports') }}</span>
|
||||
</h2>
|
||||
|
||||
<li class="menu nav-item">
|
||||
<a href="https://vristo.sbthemes.com" target="_blank" class="nav-link group">
|
||||
<div class="flex items-center">
|
||||
<icon-menu-documentation class="shrink-0 group-hover:!text-primary" />
|
||||
|
||||
<span class="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">{{
|
||||
$t('documentation')
|
||||
}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</perfect-scrollbar>
|
||||
</client-only>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { useAppStore } from '@/stores/index';
|
||||
import VueCollapsible from 'vue-height-collapsible/vue3';
|
||||
const store = useAppStore();
|
||||
const activeDropdown: any = ref('');
|
||||
const subActive: any = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
const selector = document.querySelector('.sidebar ul a[href="' + window.location.pathname + '"]');
|
||||
|
||||
if (selector) {
|
||||
selector.classList.add('active');
|
||||
const ul: any = selector.closest('ul.sub-menu');
|
||||
if (ul) {
|
||||
let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link') || [];
|
||||
|
||||
if (ele.length) {
|
||||
ele = ele[0];
|
||||
setTimeout(() => {
|
||||
ele.click();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const toggleMobileMenu = () => {
|
||||
if (window.innerWidth < 1024) {
|
||||
store.toggleSidebar();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -4,8 +4,8 @@
|
||||
<div class="h-full bg-white dark:bg-[#0e1726]">
|
||||
<div class="flex items-center justify-between px-4 py-3">
|
||||
<NuxtLink to="/" class="main-logo flex shrink-0 items-center">
|
||||
<img class="ml-[5px] w-8 flex-none" src="/assets/images/logo.svg" alt="" />
|
||||
<span class="align-middle text-2xl font-semibold ltr:ml-1.5 rtl:mr-1.5 dark:text-white-light lg:inline">VRISTO</span>
|
||||
<img class="ml-[5px] w-8 flex-none" src="/assets/images/freekake.png" alt="" />
|
||||
<span class="align-middle text-2xl font-semibold ltr:ml-1.5 rtl:mr-1.5 dark:text-white-light lg:inline">ADMIN</span>
|
||||
</NuxtLink>
|
||||
<a
|
||||
href="javascript:;"
|
||||
@ -46,15 +46,6 @@
|
||||
<li>
|
||||
<NuxtLink to="/" @click="toggleMobileMenu">{{ $t('sales') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/analytics" @click="toggleMobileMenu">{{ $t('analytics') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/finance" @click="toggleMobileMenu">{{ $t('finance') }}</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/crypto" @click="toggleMobileMenu">{{ $t('crypto') }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</vue-collapsible>
|
||||
</li>
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<!-- BEGIN APP SETTING LAUNCHER -->
|
||||
<theme-customizer />
|
||||
<!-- <theme-customizer /> -->
|
||||
<!-- END APP SETTING LAUNCHER -->
|
||||
|
||||
<div class="main-container min-h-screen text-black dark:text-white-dark" :class="[store.navbar]">
|
||||
|
||||
@ -38,22 +38,8 @@ export default defineNuxtConfig({
|
||||
|
||||
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',
|
||||
@ -82,4 +68,6 @@ export default defineNuxtConfig({
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
ssr: false
|
||||
});
|
||||
6301
package-lock.json
generated
6301
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.0.22",
|
||||
"quill": "^2.0.2",
|
||||
"quill": "^1.3.7",
|
||||
"sweetalert2": "^11.14.0",
|
||||
"swiper": "^11.1.14",
|
||||
"tippy.vue": "^3.2.1",
|
||||
|
||||
@ -149,7 +149,7 @@
|
||||
sex: { required },
|
||||
type: { required },
|
||||
featured_image: { imageType, maxFileSize },
|
||||
featured_icon: { imageType, maxFileSize},
|
||||
featured_icon: { imageType, maxFileSize },
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
@ -164,7 +164,7 @@
|
||||
const featuredImageUploader = ref(null);
|
||||
const featuredIconUploader = ref(null);
|
||||
|
||||
const { data: form, refresh } = await useAsyncData('characters',
|
||||
const { data: form, refresh } = await useAsyncData('character-get',
|
||||
() => $fetch(`${config.public.apiBase}/character/characters/${route.params.id}/`, {}),
|
||||
);
|
||||
|
||||
@ -192,10 +192,6 @@
|
||||
});
|
||||
|
||||
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 () => {
|
||||
@ -219,16 +215,18 @@
|
||||
formData.append('featured_icon', form.value.featured_icon);
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}/character/characters/${route.params.id}/`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
//redirect
|
||||
router.push({ path: "/character/characters/list" });
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari..." @change="changeSearch"/>
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari... (tombol Enter untuk mencari)" @change="changeSearch"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<div class="datatable">
|
||||
@ -80,7 +80,7 @@
|
||||
sort: false,
|
||||
width: '150px'
|
||||
}
|
||||
]);
|
||||
]) || [];
|
||||
|
||||
const params = reactive({
|
||||
search: null,
|
||||
@ -125,9 +125,7 @@
|
||||
};
|
||||
|
||||
const changeSearch = (data: any) => {
|
||||
console.log(data);
|
||||
params.current_page = 1;
|
||||
console.log(params);
|
||||
};
|
||||
|
||||
const viewData = (data: any) => {
|
||||
|
||||
@ -94,8 +94,8 @@
|
||||
</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>
|
||||
@ -106,27 +106,43 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { integer, minValue,required } from '@vuelidate/validators';
|
||||
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: { required },
|
||||
featured_icon: { required },
|
||||
featured_image: { imageType, maxFileSize },
|
||||
featured_icon: { imageType, maxFileSize },
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const featuredImageUploader = ref(null);
|
||||
const featuredIconUploader = ref(null);
|
||||
|
||||
const params = reactive({
|
||||
search: null,
|
||||
current_page: 1,
|
||||
@ -147,29 +163,29 @@
|
||||
}
|
||||
);
|
||||
|
||||
const { data: form } = await useAsyncData('skins',
|
||||
() => $fetch(`${config.public.apiBase}character/character-skins/${route.params.id}`, {})
|
||||
const { data: form, refresh } = await useAsyncData('skin-get',
|
||||
() => $fetch(`${config.public.apiBase}/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;
|
||||
|
||||
// single image upload
|
||||
new FileUploadWithPreview('featuredImage', {
|
||||
featuredImageUploader.value = new FileUploadWithPreview('featuredImage', {
|
||||
images: {
|
||||
baseImage: form.value.featured_image || '/assets/images/file-preview.svg',
|
||||
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',
|
||||
baseImage: form.value?.featured_icon || '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
},
|
||||
});
|
||||
@ -188,23 +204,25 @@
|
||||
formData.append('character', form.value.character.id);
|
||||
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/character-skins/${route.params.id}/`, {
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}/character/character-skins/${route.params.id}/`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
//redirect
|
||||
router.push({ path: "/character/skins/list" });
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -219,20 +237,31 @@
|
||||
return
|
||||
}
|
||||
|
||||
form.value.featured_image = file
|
||||
form.value.featured_image = file;
|
||||
}
|
||||
|
||||
function handleIconChange(event: { target: { files: any[]; }; }) {
|
||||
const file = event.target.files[0]
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file) return
|
||||
if (!file) return;
|
||||
|
||||
const allowed = ['image/png', 'image/jpeg', 'image/jpg']
|
||||
const allowed = ['image/png', 'image/jpeg', 'image/jpg'];
|
||||
if (!allowed.includes(file.type)) {
|
||||
alert('Hanya PNG yang diizinkan')
|
||||
return
|
||||
alert('Hanya PNG yang diizinkan');
|
||||
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>
|
||||
@ -94,8 +94,8 @@
|
||||
</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" /> {{ isLoading ? 'Menyimpan...' : 'Simpan' }}</button>
|
||||
<button type="button" class="btn btn-dark !py-1 ml-1" @click="resetForm"><icon-restore class="me-1" />Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -107,7 +107,7 @@
|
||||
<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';
|
||||
@ -116,19 +116,29 @@
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
decription: '',
|
||||
character: '',
|
||||
description: '',
|
||||
character: 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 },
|
||||
character: { required },
|
||||
featured_image: { required },
|
||||
featured_icon: { required },
|
||||
featured_image: { required, imageType, maxFileSize },
|
||||
featured_icon: { required, imageType, maxFileSize },
|
||||
}
|
||||
};
|
||||
const $validate = useVuelidate(rules, { form });
|
||||
@ -143,7 +153,7 @@
|
||||
});
|
||||
|
||||
const { data: characters } = await useAsyncData('characters',
|
||||
() => $fetch(`${config.public.apiBase}character/characters/`, {
|
||||
() => $fetch(`${config.public.apiBase}/character/characters/`, {
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: 50
|
||||
@ -152,21 +162,24 @@
|
||||
watch: [params]
|
||||
}
|
||||
);
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const featuredImageUploader = ref(null);
|
||||
const featuredIconUploader = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
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: '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
},
|
||||
});
|
||||
|
||||
// single image upload
|
||||
new FileUploadWithPreview('featuredIcon', {
|
||||
featuredIconUploader.value = new FileUploadWithPreview('featuredIcon', {
|
||||
images: {
|
||||
baseImage: '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
@ -194,17 +207,18 @@
|
||||
formData.append('featured_icon', form.value.featured_icon);
|
||||
}
|
||||
|
||||
await $fetch(`${config.public.apiBase}character/character-skins/`, {
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}/character/character-skins/`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
//redirect
|
||||
router.push({ path: "/character/skins/list" });
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
//assign response error data to state "errors"
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -235,4 +249,21 @@
|
||||
|
||||
form.value.featured_icon = file
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
character: null,
|
||||
featured_image: '',
|
||||
featured_icon: '',
|
||||
};
|
||||
|
||||
isSubmitted.value = false;
|
||||
$validate.value.form.$reset();
|
||||
|
||||
featuredImageUploader?.value?.clearPreviewPanel();
|
||||
featuredIconUploader?.value?.clearPreviewPanel();
|
||||
|
||||
};
|
||||
</script>
|
||||
@ -19,14 +19,14 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari..." @change="changeSearch"/>
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari...(tombol Enter untuk mencari)" @change="changeSearch"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<div class="datatable">
|
||||
<vue3-datatable
|
||||
:rows="skins?.results"
|
||||
:rows="rows"
|
||||
:columns="cols"
|
||||
:totalRows="skins?.count"
|
||||
:totalRows="totalRows"
|
||||
:isServerMode=true
|
||||
:page="params.current_page"
|
||||
:pageSize="params.pagesize"
|
||||
@ -40,8 +40,8 @@
|
||||
previousArrow='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-4.5 h-4.5 rtl:rotate-180"> <path d="M15 5L9 12L15 19" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>'
|
||||
nextArrow='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-4.5 h-4.5 rtl:rotate-180"> <path d="M9 5L15 12L9 19" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>'
|
||||
>
|
||||
<template #id="data">
|
||||
<div class="flex gap-1">
|
||||
<template #actions="data">
|
||||
<div class="flex justify-end gap-1">
|
||||
<button type="button" class="btn btn-success !py-1" @click="viewData(data.value)">
|
||||
<icon-edit class="me-1" />
|
||||
Edit
|
||||
@ -72,8 +72,14 @@
|
||||
ref([
|
||||
{ field: 'name', title: 'Nama' },
|
||||
{ field: 'character.name', title: 'Karakter' },
|
||||
{ field: 'id', title: 'Aksi' },
|
||||
|
||||
{
|
||||
field: 'actions',
|
||||
title: 'Aksi',
|
||||
headerClass: 'text-right',
|
||||
cellClass: 'text-right',
|
||||
sort: false,
|
||||
width: '150px'
|
||||
}
|
||||
]) || [];
|
||||
|
||||
const params = reactive({
|
||||
@ -84,6 +90,8 @@
|
||||
sort_direction: 'asc',
|
||||
});
|
||||
|
||||
const rows = computed(() => skins.value?.results ?? []);
|
||||
const totalRows = computed(() => skins.value?.count ?? 0);
|
||||
const { data: skins } = await useAsyncData('skins',
|
||||
() => {
|
||||
return $fetch(`${config.public.apiBase}/character/character-skins/`, {
|
||||
@ -106,9 +114,7 @@
|
||||
};
|
||||
|
||||
const changeSearch = (data: any) => {
|
||||
console.log(data);
|
||||
params.current_page = 1;
|
||||
console.log(params);
|
||||
};
|
||||
|
||||
const viewData = (data: any) => {
|
||||
|
||||
@ -38,6 +38,28 @@
|
||||
<p class="text-danger mt-1">Slug konten 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="statuses?.results"
|
||||
class="custom-multiselect"
|
||||
:searchable="true"
|
||||
placeholder="Pilih status konten"
|
||||
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">Jenis status harus diisi</p>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="form.status?.value === 'published'" :class="{ 'has-error': $validate.form.posted_at.$error, 'has-success': isSubmitted && !$validate.form.posted_at.$error }">
|
||||
<label for="posted_at">Tanggal Publikasi</label>
|
||||
<flat-pickr id="posted_at" v-model="form.posted_at" class="form-input" :config="basic"></flat-pickr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 gap-2">
|
||||
<div :class="{ 'has-error': $validate.form.theme.$error, 'has-success': isSubmitted && !$validate.form.theme.$error }">
|
||||
@ -51,8 +73,8 @@
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="theme"
|
||||
track-by="id"
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.theme.$error">
|
||||
<p class="text-danger mt-1">Tema konten harus diisi</p>
|
||||
@ -60,7 +82,7 @@
|
||||
</div>
|
||||
<div :class="{ 'has-error': $validate.form.topic.$error, 'has-success': isSubmitted && !$validate.form.topic.$error }">
|
||||
<label for="topic">Topik</label>
|
||||
<multiselect id="topic"
|
||||
<multiselect v-if="topics" id="topic"
|
||||
v-model="form.topic"
|
||||
:options="topics?.results"
|
||||
class="custom-multiselect"
|
||||
@ -69,8 +91,8 @@
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="topic"
|
||||
track-by="id"
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.topic.$error">
|
||||
<p class="text-danger mt-1">Topik konten harus diisi</p>
|
||||
@ -94,6 +116,24 @@
|
||||
<p class="text-danger mt-1">Format konten harus diisi</p>
|
||||
</template>
|
||||
</div>
|
||||
<div :class="{ 'has-error': $validate.form.type.$error, 'has-success': isSubmitted && !$validate.form.type.$error }">
|
||||
<label for="type">Jenis Konten</label>
|
||||
<multiselect id="theme"
|
||||
v-model="form.type"
|
||||
:options="types?.results"
|
||||
class="custom-multiselect"
|
||||
:searchable="true"
|
||||
placeholder="Pilih jenis konten"
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.type.$error">
|
||||
<p class="text-danger mt-1">Jenis konten 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 }">
|
||||
@ -107,7 +147,9 @@
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-1 gap-2">
|
||||
<div :class="{ 'has-error': $validate.form.content.$error, 'has-success': isSubmitted && !$validate.form.content.$error }">
|
||||
<label for="content">Detail Konten</label>
|
||||
<textarea id="content" rows="3" class="form-textarea" v-model="form.content"></textarea>
|
||||
<div style="min-height: 300px">
|
||||
<quillEditor v-if="form.content" id="content" ref="content" v-model:value="form.content" :options="quilloptions" style="min-height: 200px; height: 100%;" contentType="html"></quillEditor>
|
||||
</div>
|
||||
<template v-if="isSubmitted && $validate.form.content.$error">
|
||||
<p class="text-danger mt-1">Detail konten harus diisi</p>
|
||||
</template>
|
||||
@ -167,8 +209,8 @@
|
||||
</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" /> {{ isLoading ? 'Menyimpan...' : 'Simpan' }}</button>
|
||||
<button type="button" class="btn btn-dark !py-1 ml-1" @click="resetForm"><icon-restore class="me-1" />Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -179,13 +221,27 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { integer, minValue,required } from '@vuelidate/validators';
|
||||
import { helpers, integer, minValue,required, requiredIf } from '@vuelidate/validators';
|
||||
import Multiselect from '@suadelabs/vue3-multiselect';
|
||||
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
||||
import '@/assets/css/file-upload-preview.css';
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'vue3-quill/lib/vue3-quill.css';
|
||||
|
||||
useHead({ title: 'Edit Konten' });
|
||||
|
||||
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: {
|
||||
@ -199,20 +255,30 @@
|
||||
grades: { required },
|
||||
point: { required, integer, minValue: minValue(0) },
|
||||
coin: { required, integer, minValue: minValue(0) },
|
||||
featured_image: { required },
|
||||
featured_image: { imageType, maxFileSize },
|
||||
status: { required },
|
||||
type: { required },
|
||||
posted_at: {
|
||||
required: requiredIf(() => form.value.status?.value === 'published'),
|
||||
},
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const { data: formats } = await useAsyncData('formats',
|
||||
() => $fetch(`${config.public.apiBase}/content/formats/`)
|
||||
);
|
||||
const quilloptions = ref({});
|
||||
|
||||
const basic: any = ref({
|
||||
dateFormat: 'Y-m-d',
|
||||
position: 'auto left',
|
||||
minDate: "today",
|
||||
});
|
||||
|
||||
const { data: form } = await useAsyncData('contents',
|
||||
const { data: form, refresh} = await useAsyncData('content-get',
|
||||
async () => {
|
||||
const content = await $fetch(`${config.public.apiBase}content/contents/${route.params.id}`, {});
|
||||
console.log('Fetching content data for ID:', route.params.id);
|
||||
const content = await $fetch(`${config.public.apiBase}/content/contents/${route.params.id}/`, {});
|
||||
|
||||
return {
|
||||
...content,
|
||||
@ -220,68 +286,103 @@
|
||||
};
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
if (formats.value?.results?.length && typeof form.value.format === 'string') {
|
||||
const found = formats.value.results.find(f => f.value === form.value.format)
|
||||
if (found) {
|
||||
await nextTick();
|
||||
form.value.format = found;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const $validate = useVuelidate(rules, { form });
|
||||
|
||||
const params = reactive({
|
||||
current_page: 1,
|
||||
pagesize: 10,
|
||||
sort_column: 'id',
|
||||
sort_direction: 'asc',
|
||||
});
|
||||
|
||||
const { data: formats } = await useAsyncData('formats',
|
||||
() => $fetch(`${config.public.apiBase}/content/formats/`)
|
||||
);
|
||||
|
||||
const { data: themes } = await useAsyncData('themes',
|
||||
() => $fetch(`${config.public.apiBase}content/themes/`, {
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: 50
|
||||
}
|
||||
}), {
|
||||
watch: [params]
|
||||
}
|
||||
() => $fetch(`${config.public.apiBase}/content/themes/`)
|
||||
);
|
||||
|
||||
const { data: topics, refresh: refreshTopics } = await useAsyncData('topics',
|
||||
() => $fetch(`${config.public.apiBase}content/topics/`, {
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: 50,
|
||||
theme: form.value.theme ? form.value.theme.id : ''
|
||||
}
|
||||
}), {
|
||||
watch: [params, () => form.value?.theme]
|
||||
}
|
||||
// const { data: topics, refresh: refreshTopics } = await useAsyncData('topics',
|
||||
// () => $fetch(`${config.public.apiBase}/content/topics/${form.value.theme?.value}/`), {
|
||||
// watch: [() => form.value.theme?.value],
|
||||
// }
|
||||
// );
|
||||
|
||||
const { data: types } = await useAsyncData('types',
|
||||
() => $fetch(`${config.public.apiBase}/content/types/`)
|
||||
);
|
||||
|
||||
watch(() => form.value?.theme, async (val) => {
|
||||
form.value.topic = null;
|
||||
if (val && formats.value) {
|
||||
await refreshTopics();
|
||||
const { data: statuses } = await useAsyncData('statuses',
|
||||
() => $fetch(`${config.public.apiBase}/content/statuses/`)
|
||||
);
|
||||
|
||||
const topics = ref({ results: [] });
|
||||
const fetchTopics = async () => {
|
||||
if (!form.value.theme?.value) return;
|
||||
try {
|
||||
const res = await $fetch(`${config.public.apiBase}/content/topics/${form.value.theme.value}/`);
|
||||
topics.value = res;
|
||||
|
||||
// mapping kembali topik jika perlu
|
||||
if (typeof form.value.topic === 'string') {
|
||||
form.value.topic = res.results.find(t => t.value === form.value.topic) ?? null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Gagal mengambil topik:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
watchEffect(() => {
|
||||
if (form.value && formats.value?.results?.length && typeof form.value.format === 'string') {
|
||||
form.value.format = formats.value.results.find(f => f.value === form.value.format) ?? null;
|
||||
}
|
||||
|
||||
if (form.value && themes.value?.results?.length && typeof form.value.theme === 'string') {
|
||||
form.value.theme = themes.value.results.find(t => t.value === form.value.theme) ?? null;
|
||||
}
|
||||
|
||||
if (form.value && topics.value?.results?.length && typeof form.value.topic === 'string') {
|
||||
form.value.topic = topics.value.results.find(t => t.value === form.value.topic) ?? null;
|
||||
}
|
||||
|
||||
if (form.value && types.value?.results?.length && typeof form.value.type === 'string') {
|
||||
form.value.type = types.value.results.find(t => t.value === form.value.type) ?? null;
|
||||
}
|
||||
|
||||
if (form.value && statuses.value?.results?.length && typeof form.value.status === 'string') {
|
||||
form.value.status = statuses.value.results.find(s => s.value === form.value.status) ?? null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
watchEffect(async () => {
|
||||
if (!form.value.theme || typeof form.value.theme === 'string') {
|
||||
if (themes.value?.results?.length && typeof form.value.theme === 'string') {
|
||||
form.value.theme = themes.value.results.find(t => t.value === form.value.theme) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
if (form.value.theme?.value) {
|
||||
await fetchTopics();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const featuredImageUploader = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
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 ? form.value.featured_image : '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
},
|
||||
});
|
||||
|
||||
if (!form.value.status && statuses?.value?.results?.length) {
|
||||
form.value.status = statuses.value.results.find(s => s.value === 'draft');
|
||||
}
|
||||
if (!form.value.type && types?.value?.results?.length) {
|
||||
form.value.type = types.value.results.find(t => t.value === 'content');
|
||||
}
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
@ -296,8 +397,8 @@
|
||||
formData.append('title', form.value.title);
|
||||
formData.append('slug', form.value.slug);
|
||||
formData.append('format', form.value.format.value);
|
||||
formData.append('topic', form.value.topic.id);
|
||||
formData.append('theme', form.value.theme.id);
|
||||
formData.append('topic', form.value.topic.value);
|
||||
formData.append('theme', form.value.theme.value);
|
||||
formData.append('description', form.value.description);
|
||||
formData.append('content', form.value.content);
|
||||
formData.append('data', JSON.stringify(form.value.data));
|
||||
@ -306,34 +407,63 @@
|
||||
formData.append('coin', form.value.coin);
|
||||
formData.append('color', form.value.color);
|
||||
|
||||
if (form.value.featured_image) {
|
||||
formData.append('type', form.value.type.value);
|
||||
formData.append('status', form.value.status.value);
|
||||
if (form.value.status?.value === 'published' && form.value.posted_at) {
|
||||
formData.append('posted_at', form.value.posted_at);
|
||||
}
|
||||
if (form.value.status?.value === 'archived') {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
formData.append('archived_at', today);
|
||||
}
|
||||
|
||||
if (form.value.featured_image && typeof form.value.featured_image !== 'string') {
|
||||
formData.append('featured_image', form.value.featured_image);
|
||||
}
|
||||
|
||||
await $fetch(`${config.public.apiBase}content/contents/${route.params.id}/`, {
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}/content/contents/${route.params.id}/`, {
|
||||
method: 'PUT',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
//redirect
|
||||
router.push({ path: "/content/contents/list" });
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleImageChange(event: { target: { files: any[]; }; }) {
|
||||
const file = event.target.files[0]
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file) return
|
||||
if (!file) return;
|
||||
|
||||
const allowed = ['image/png']
|
||||
const allowed = ['image/png', 'image/jpeg', 'image/jpg'];
|
||||
if (!allowed.includes(file.type)) {
|
||||
alert('Hanya PNG yang diizinkan')
|
||||
return
|
||||
alert('Hanya PNG atau JPG yang diizinkan');
|
||||
return;
|
||||
}
|
||||
|
||||
form.value.featured_image = file
|
||||
form.value.featured_image = file;
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
refresh();
|
||||
|
||||
if (!form.value.status && statuses?.value?.results?.length) {
|
||||
form.value.status = statuses.value.results.find(s => s.value === 'draft');
|
||||
}
|
||||
if (!form.value.type && types?.value?.results?.length) {
|
||||
form.value.type = types.value.results.find(t => t.value === 'content');
|
||||
}
|
||||
|
||||
isSubmitted.value = false;
|
||||
$validate.value.form.$reset();
|
||||
|
||||
featuredImageUploader?.value?.clearPreviewPanel();
|
||||
|
||||
}
|
||||
</script>
|
||||
@ -38,6 +38,28 @@
|
||||
<p class="text-danger mt-1">Slug konten 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="statuses?.results"
|
||||
class="custom-multiselect"
|
||||
:searchable="true"
|
||||
placeholder="Pilih status konten"
|
||||
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">Jenis status harus diisi</p>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="form.status?.value === 'published'" :class="{ 'has-error': $validate.form.posted_at.$error, 'has-success': isSubmitted && !$validate.form.posted_at.$error }">
|
||||
<label for="posted_at">Tanggal Publikasi</label>
|
||||
<flat-pickr id="posted_at" v-model="form.posted_at" class="form-input" :config="basic"></flat-pickr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 gap-2">
|
||||
<div :class="{ 'has-error': $validate.form.theme.$error, 'has-success': isSubmitted && !$validate.form.theme.$error }">
|
||||
@ -51,8 +73,8 @@
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="theme"
|
||||
track-by="id"
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.theme.$error">
|
||||
<p class="text-danger mt-1">Tema konten harus diisi</p>
|
||||
@ -69,8 +91,8 @@
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="topic"
|
||||
track-by="id"
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.topic.$error">
|
||||
<p class="text-danger mt-1">Topik konten harus diisi</p>
|
||||
@ -94,6 +116,24 @@
|
||||
<p class="text-danger mt-1">Format konten harus diisi</p>
|
||||
</template>
|
||||
</div>
|
||||
<div :class="{ 'has-error': $validate.form.type.$error, 'has-success': isSubmitted && !$validate.form.type.$error }">
|
||||
<label for="type">Jenis Konten</label>
|
||||
<multiselect id="theme"
|
||||
v-model="form.type"
|
||||
:options="types?.results"
|
||||
class="custom-multiselect"
|
||||
:searchable="true"
|
||||
placeholder="Pilih jenis konten"
|
||||
selected-label=""
|
||||
select-label=""
|
||||
deselect-label=""
|
||||
label="label"
|
||||
track-by="value"
|
||||
></multiselect>
|
||||
<template v-if="isSubmitted && $validate.form.type.$error">
|
||||
<p class="text-danger mt-1">Jenis konten 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 }">
|
||||
@ -107,7 +147,9 @@
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-1 gap-2">
|
||||
<div :class="{ 'has-error': $validate.form.content.$error, 'has-success': isSubmitted && !$validate.form.content.$error }">
|
||||
<label for="content">Detail Konten</label>
|
||||
<textarea id="content" rows="3" class="form-textarea" v-model="form.content"></textarea>
|
||||
<div style="min-height: 300px">
|
||||
<quillEditor id="content" ref="content" v-model:value="form.content" :options="quilloptions" style="min-height: 200px; height: 100%;"></quillEditor>
|
||||
</div>
|
||||
<template v-if="isSubmitted && $validate.form.content.$error">
|
||||
<p class="text-danger mt-1">Detail konten harus diisi</p>
|
||||
</template>
|
||||
@ -167,8 +209,8 @@
|
||||
</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" /> {{ isLoading ? 'Menyimpan...' : 'Simpan' }}</button>
|
||||
<button type="button" class="btn btn-dark !py-1 ml-1" @click="resetForm"><icon-restore class="me-1" />Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -180,11 +222,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { integer, minValue, required } from '@vuelidate/validators';
|
||||
import { integer, minValue, required, requiredIf } from '@vuelidate/validators';
|
||||
import Multiselect from '@suadelabs/vue3-multiselect';
|
||||
import '@suadelabs/vue3-multiselect/dist/vue3-multiselect.css';
|
||||
import '@/assets/css/file-upload-preview.css';
|
||||
|
||||
import 'flatpickr/dist/flatpickr.css';
|
||||
import flatPickr from 'vue-flatpickr-component';
|
||||
import 'vue3-quill/lib/vue3-quill.css';
|
||||
|
||||
useHead({ title: 'Tambah Konten' });
|
||||
|
||||
const form = ref({
|
||||
@ -193,7 +238,7 @@
|
||||
theme: null,
|
||||
topic: null,
|
||||
format: '',
|
||||
decription: '',
|
||||
description: '',
|
||||
content: '',
|
||||
data: '',
|
||||
grades: [],
|
||||
@ -201,6 +246,9 @@
|
||||
coin: 0,
|
||||
featured_image: '',
|
||||
color: '',
|
||||
type: '',
|
||||
status: '',
|
||||
posted_at: null,
|
||||
});
|
||||
const isSubmitted = ref(false);
|
||||
const rules = {
|
||||
@ -216,66 +264,72 @@
|
||||
point: { required, integer, minValue: minValue(0) },
|
||||
coin: { required, integer, minValue: minValue(0) },
|
||||
featured_image: { required },
|
||||
status: { required },
|
||||
type: { required },
|
||||
posted_at: {
|
||||
required: requiredIf(() => form.value.status?.value === 'published'),
|
||||
},
|
||||
}
|
||||
};
|
||||
const $validate = useVuelidate(rules, { form });
|
||||
const router = useRouter();
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const params = reactive({
|
||||
current_page: 1,
|
||||
pagesize: 10,
|
||||
sort_column: 'id',
|
||||
sort_direction: 'asc',
|
||||
const quilloptions = ref({});
|
||||
|
||||
const basic: any = ref({
|
||||
dateFormat: 'Y-m-d',
|
||||
position: 'auto left',
|
||||
minDate: "today",
|
||||
});
|
||||
|
||||
const { data: themes } = await useAsyncData('themes',
|
||||
() => $fetch(`${config.public.apiBase}content/themes/`, {
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: 50
|
||||
}
|
||||
}), {
|
||||
watch: [params]
|
||||
}
|
||||
() => $fetch(`${config.public.apiBase}/content/themes/`)
|
||||
);
|
||||
|
||||
const { data: topics } = await useAsyncData('topics',
|
||||
() => $fetch(`${config.public.apiBase}content/topics/`, {
|
||||
params: {
|
||||
page: params.current_page,
|
||||
page_size: 50,
|
||||
theme: form.value.theme ? form.value.theme.id : ''
|
||||
}
|
||||
}), {
|
||||
watch: [params]
|
||||
() => $fetch(`${config.public.apiBase}/content/topics/${form.value.theme?.value}/`), {
|
||||
watch: [() => form.value.theme?.value]
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => form.value.theme, async (val) => {
|
||||
form.value.topic = null
|
||||
if (val) {
|
||||
refreshNuxtData('topics');
|
||||
} else {
|
||||
|
||||
}
|
||||
watch(() => form.value.theme, () => {
|
||||
form.value.topic = null;
|
||||
});
|
||||
|
||||
const { data: formats } = await useAsyncData('formats',
|
||||
() => $fetch(`${config.public.apiBase}content/formats/`)
|
||||
() => $fetch(`${config.public.apiBase}/content/formats/`)
|
||||
);
|
||||
|
||||
const { data: types } = await useAsyncData('types',
|
||||
() => $fetch(`${config.public.apiBase}/content/types/`)
|
||||
);
|
||||
|
||||
const { data: statuses } = await useAsyncData('statuses',
|
||||
() => $fetch(`${config.public.apiBase}/content/statuses/`)
|
||||
);
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const featuredImageUploader = ref(null);
|
||||
|
||||
onMounted(async () => {
|
||||
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: '/assets/images/file-preview.svg',
|
||||
backgroundImage: '',
|
||||
},
|
||||
});
|
||||
|
||||
if (!form.value.status && statuses?.value?.results?.length) {
|
||||
form.value.status = statuses.value.results.find(s => s.value === 'draft');
|
||||
}
|
||||
if (!form.value.type && types?.value?.results?.length) {
|
||||
form.value.type = types.value.results.find(t => t.value === 'content');
|
||||
}
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
@ -290,32 +344,47 @@
|
||||
formData.append('title', form.value.title);
|
||||
formData.append('slug', form.value.slug);
|
||||
formData.append('format', form.value.format.value);
|
||||
formData.append('topic', form.value.topic.id);
|
||||
formData.append('theme', form.value.theme.id);
|
||||
formData.append('topic', form.value.topic.value);
|
||||
formData.append('theme', form.value.theme.value);
|
||||
formData.append('description', form.value.description);
|
||||
formData.append('content', form.value.content);
|
||||
formData.append('data', form.value.data);
|
||||
if (!form.value.data) {
|
||||
formData.append('data', '{}');
|
||||
} else {
|
||||
formData.append('data', form.value.data);
|
||||
}
|
||||
formData.append('grades', JSON.stringify(form.value.grades));
|
||||
formData.append('point', form.value.point);
|
||||
formData.append('coin', form.value.coin);
|
||||
formData.append('color', form.value.color);
|
||||
|
||||
formData.append('type', form.value.type.value);
|
||||
formData.append('status', form.value.status.value);
|
||||
if (form.value.status?.value === 'published' && form.value.posted_at) {
|
||||
formData.append('posted_at', form.value.posted_at);
|
||||
}
|
||||
if (form.value.status?.value === 'archived') {
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
formData.append('archived_at', today);
|
||||
}
|
||||
|
||||
if (form.value.featured_image) {
|
||||
formData.append('featured_image', form.value.featured_image);
|
||||
}
|
||||
|
||||
console.log(formData);
|
||||
await $fetch(`${config.public.apiBase}content/contents/`, {
|
||||
isLoading.value = true;
|
||||
await $fetch(`${config.public.apiBase}/content/contents/`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(() => {
|
||||
//redirect
|
||||
router.push({ path: "/content/contents/list" });
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
//assign response error data to state "errors"
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -324,12 +393,46 @@
|
||||
|
||||
if (!file) return
|
||||
|
||||
const allowed = ['image/png']
|
||||
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_image = file
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.value = {
|
||||
title: '',
|
||||
slug: '',
|
||||
theme: null,
|
||||
topic: null,
|
||||
format: '',
|
||||
description: '',
|
||||
content: '',
|
||||
data: '',
|
||||
grades: [],
|
||||
point: 0,
|
||||
coin: 0,
|
||||
featured_image: '',
|
||||
color: '',
|
||||
type: '',
|
||||
status: '',
|
||||
posted_at: null,
|
||||
};
|
||||
|
||||
if (!form.value.status && statuses?.value?.results?.length) {
|
||||
form.value.status = statuses.value.results.find(s => s.value === 'draft');
|
||||
}
|
||||
if (!form.value.type && types?.value?.results?.length) {
|
||||
form.value.type = types.value.results.find(t => t.value === 'content');
|
||||
}
|
||||
|
||||
isSubmitted.value = false;
|
||||
$validate.value.form.$reset();
|
||||
|
||||
featuredImageUploader?.value?.clearPreviewPanel();
|
||||
|
||||
}
|
||||
</script>
|
||||
@ -19,14 +19,14 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari..." @change="changeSearch"/>
|
||||
<input v-model.lazy="params.search" type="text" class="form-input max-w-xs" placeholder="Cari...(tombol Enter untuk mencari)" @change="changeSearch"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<div class="datatable">
|
||||
<vue3-datatable
|
||||
:rows="contents?.results"
|
||||
:rows="rows"
|
||||
:columns="cols"
|
||||
:totalRows="contents?.count"
|
||||
:totalRows="totalRows"
|
||||
:isServerMode=true
|
||||
:page="params.current_page"
|
||||
:pageSize="params.pagesize"
|
||||
@ -40,8 +40,8 @@
|
||||
previousArrow='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-4.5 h-4.5 rtl:rotate-180"> <path d="M15 5L9 12L15 19" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>'
|
||||
nextArrow='<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-4.5 h-4.5 rtl:rotate-180"> <path d="M9 5L15 12L9 19" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </svg>'
|
||||
>
|
||||
<template #id="data">
|
||||
<div class="flex gap-1">
|
||||
<template #actions="data">
|
||||
<div class="flex justify-end gap-1">
|
||||
<button type="button" class="btn btn-success !py-1" @click="viewData(data.value)">
|
||||
<icon-edit class="me-1" />
|
||||
Edit
|
||||
@ -71,11 +71,35 @@
|
||||
const cols =
|
||||
ref([
|
||||
{ field: 'title', title: 'Judul' },
|
||||
{ field: 'theme.theme', title: 'Tema' },
|
||||
{ field: 'topic.topic', title: 'Topik' },
|
||||
{ field: 'format', title: 'Format' },
|
||||
{ field: 'id', title: 'Aksi' },
|
||||
|
||||
{
|
||||
field: 'theme',
|
||||
title: 'Tema',
|
||||
cellRenderer: (row: any) => row.theme_display
|
||||
},
|
||||
{
|
||||
field: 'topic',
|
||||
title: 'Topik',
|
||||
cellRenderer: (row: any) => row.topic_display
|
||||
},
|
||||
{ field: 'format_display', title: 'Format' },
|
||||
{
|
||||
field: 'type',
|
||||
title: 'Jenis',
|
||||
cellRenderer: (row: any) => row.type_display
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: 'Status',
|
||||
cellRenderer: (row: any) => row.status_display
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
title: 'Aksi',
|
||||
headerClass: 'text-right',
|
||||
cellClass: 'text-right',
|
||||
sort: false,
|
||||
width: '150px'
|
||||
}
|
||||
]) || [];
|
||||
|
||||
const params = reactive({
|
||||
@ -86,6 +110,8 @@
|
||||
sort_direction: 'asc',
|
||||
});
|
||||
|
||||
const rows = computed(() => contents.value?.results ?? []);
|
||||
const totalRows = computed(() => contents.value?.count ?? 0);
|
||||
const { data: contents } = await useAsyncData('contents',
|
||||
() => {
|
||||
return $fetch(`${config.public.apiBase}/content/contents/`, {
|
||||
@ -108,9 +134,7 @@
|
||||
};
|
||||
|
||||
const changeSearch = (data: any) => {
|
||||
console.log(data);
|
||||
params.current_page = 1;
|
||||
console.log(params);
|
||||
};
|
||||
|
||||
const viewData = (data: any) => {
|
||||
|
||||
5
plugins/vue3-quill.client.ts
Normal file
5
plugins/vue3-quill.client.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { quillEditor } from 'vue3-quill';
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.component('quillEditor', quillEditor);
|
||||
});
|
||||
BIN
public/assets/images/freekake.png
Normal file
BIN
public/assets/images/freekake.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Loading…
Reference in New Issue
Block a user