forked from geolba/tethys.backend
initial commit
This commit is contained in:
commit
4fc3bb0a01
202 changed files with 41729 additions and 0 deletions
62
resources/css/_checkbox-radio-switch.css
Normal file
62
resources/css/_checkbox-radio-switch.css
Normal file
|
@ -0,0 +1,62 @@
|
|||
.checkbox, .radio, .switch {
|
||||
@apply inline-flex items-center cursor-pointer relative;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox], .radio input[type=radio], .switch input[type=checkbox] {
|
||||
@apply absolute left-0 opacity-0 -z-1;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check, .switch input[type=checkbox]+.check {
|
||||
@apply border-gray-700 border transition-colors duration-200 dark:bg-slate-800;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]:focus+.check, .radio input[type=radio]:focus+.check, .switch input[type=checkbox]:focus+.check {
|
||||
@apply ring ring-blue-700;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]+.check, .radio input[type=radio]+.check {
|
||||
@apply block w-5 h-5;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]+.check {
|
||||
@apply rounded;
|
||||
}
|
||||
|
||||
.switch input[type=checkbox]+.check {
|
||||
@apply flex items-center shrink-0 w-12 h-6 p-0.5 bg-gray-200;
|
||||
}
|
||||
|
||||
.radio input[type=radio]+.check, .switch input[type=checkbox]+.check, .switch input[type=checkbox]+.check:before {
|
||||
@apply rounded-full;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]:checked+.check, .radio input[type=radio]:checked+.check {
|
||||
@apply bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4;
|
||||
}
|
||||
|
||||
.checkbox input[type=checkbox]:checked+.check {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.radio input[type=radio]:checked+.check {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.switch input[type=checkbox]:checked+.check {
|
||||
@apply bg-blue-600 border-blue-600;
|
||||
}
|
||||
|
||||
.switch input[type=checkbox]+.check:before {
|
||||
content: '';
|
||||
@apply block w-5 h-5 bg-white border border-gray-700;
|
||||
}
|
||||
|
||||
.switch input[type=checkbox]:checked+.check:before {
|
||||
transform: translate3d(110%, 0 ,0);
|
||||
@apply border-blue-600;
|
||||
}
|
||||
|
||||
.checkbox .control-label, .radio .control-label, .switch .control-label {
|
||||
@apply pl-2;
|
||||
}
|
||||
|
20
resources/css/_progress.css
Normal file
20
resources/css/_progress.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
progress {
|
||||
@apply h-3 rounded-full overflow-hidden;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
@apply bg-blue-200;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
@apply bg-blue-500;
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
@apply bg-blue-500;
|
||||
}
|
||||
|
||||
progress::-ms-fill {
|
||||
@apply bg-blue-500 border-0;
|
||||
}
|
||||
|
38
resources/css/_scrollbars.css
Normal file
38
resources/css/_scrollbars.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
html {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #9ca3af #e5e7eb;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-track {
|
||||
@apply bg-gray-200;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-400 rounded;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-gray-500;
|
||||
}
|
||||
|
||||
html.dark-scrollbars {
|
||||
scrollbar-color: #374151 #111827;
|
||||
}
|
||||
|
||||
html.dark-scrollbars::-webkit-scrollbar-track {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
|
||||
html.dark-scrollbars::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
|
||||
html.dark-scrollbars::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-gray-600;
|
||||
}
|
||||
|
47
resources/css/_table.css
Normal file
47
resources/css/_table.css
Normal file
|
@ -0,0 +1,47 @@
|
|||
table {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
thead {
|
||||
@apply hidden lg:table-header-group;
|
||||
}
|
||||
|
||||
tr {
|
||||
@apply max-w-full block relative border-b-4 border-gray-100
|
||||
lg:table-row lg:border-b-0 dark:border-slate-800;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
@apply border-b-0;
|
||||
}
|
||||
|
||||
td:not(:first-child) {
|
||||
@apply lg:border-l lg:border-t-0 lg:border-r-0 lg:border-b-0 lg:border-gray-100 lg:dark:border-slate-700;
|
||||
}
|
||||
|
||||
th {
|
||||
@apply lg:text-left lg:p-3;
|
||||
}
|
||||
|
||||
td {
|
||||
@apply flex justify-between text-right py-3 px-4 align-top border-b border-gray-100
|
||||
lg:table-cell lg:text-left lg:p-3 lg:align-middle lg:border-b-0 dark:border-slate-800;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
@apply border-b-0;
|
||||
}
|
||||
|
||||
tbody tr, tbody tr:nth-child(odd) {
|
||||
@apply lg:hover:bg-gray-100 lg:dark:hover:bg-slate-700/70;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
@apply lg:bg-gray-50 lg:dark:bg-slate-800;
|
||||
}
|
||||
|
||||
td:before {
|
||||
content: attr(data-label);
|
||||
@apply font-semibold pr-3 text-left lg:hidden;
|
||||
}
|
||||
|
95
resources/css/app.css
Normal file
95
resources/css/app.css
Normal file
|
@ -0,0 +1,95 @@
|
|||
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap'); */
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,600,700');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import "_checkbox-radio-switch.css";
|
||||
@import "_progress.css";
|
||||
@import "_scrollbars.css";
|
||||
@import "_table.css";
|
||||
|
||||
|
||||
html, body {
|
||||
background-color: #F7F8FA;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
/* height: 100vh; */
|
||||
color: #46444c;
|
||||
position: relative;
|
||||
}
|
||||
.px-6 {
|
||||
padding-left: 0;
|
||||
/* padding-right: 1.5rem; */
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/* body:before {
|
||||
content: '';
|
||||
background: #5A45FF;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
} */
|
||||
|
||||
/* * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
} */
|
||||
|
||||
/* a {
|
||||
color: #5A45FF;
|
||||
text-decoration: none;
|
||||
} */
|
||||
|
||||
/* main {
|
||||
max-width: 620px;
|
||||
margin: auto;
|
||||
height: 100vh;
|
||||
padding: 0 30px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
} */
|
||||
|
||||
/* .title {
|
||||
font-size: 50px;
|
||||
line-height: 50px;
|
||||
margin-bottom: 10px;
|
||||
color: #17161A;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
} */
|
||||
|
||||
/* main ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
main li {
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
main li:before {
|
||||
content: '—';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
} */
|
||||
|
||||
main code {
|
||||
font-size: 16px;
|
||||
background: #e6e2ff;
|
||||
}
|
52
resources/js/Components/Admin/MenuItemList.vue
Normal file
52
resources/js/Components/Admin/MenuItemList.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script setup>
|
||||
import { Link } from "@inertiajs/vue3"
|
||||
import BaseButton from "@/Components/BaseButton.vue"
|
||||
import BaseButtons from "@/Components/BaseButtons.vue"
|
||||
import {
|
||||
mdiSquareEditOutline,
|
||||
mdiTrashCan,
|
||||
} from "@mdi/js"
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
menu: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
can: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr :key="item.id">
|
||||
<td data-label="Name">
|
||||
<div :style="{ 'margin-left': level * 20 + 'px' }">{{ item.name }}</div>
|
||||
</td>
|
||||
<td data-label="Description">
|
||||
{{ item.description }}
|
||||
</td>
|
||||
<td data-label="Enabled">
|
||||
{{ item.enabled }}
|
||||
</td>
|
||||
<td v-if="can.edit || can.delete" class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton v-if="can.edit" :route-name="route('menu.item.edit', { menu: menu.id, item: item.id })"
|
||||
color="info" :icon="mdiSquareEditOutline" small />
|
||||
<BaseButton v-if="can.delete" color="danger" :icon="mdiTrashCan" small @click="destroy(item.id)" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-for="item in item.children">
|
||||
<MenuItemList :item="item" :menu="menu" :can="can" :level="level + 1" />
|
||||
</template>
|
||||
</template>
|
161
resources/js/Components/Admin/Pagination.vue
Normal file
161
resources/js/Components/Admin/Pagination.vue
Normal file
|
@ -0,0 +1,161 @@
|
|||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const nextPageLink = computed(() => {
|
||||
let url = new URL(document.location);
|
||||
url.searchParams.set('page', props.data.current_page + 1);
|
||||
return url.href;
|
||||
// return url + '&page=' + (Number(props.data.current_page) + 1);
|
||||
});
|
||||
const prevPageLink = computed(() => {
|
||||
let url = new URL(document.location);
|
||||
url.searchParams.set('page', props.data.current_page - 1);
|
||||
return url.href;
|
||||
// return url + '&page=' + (props.data.current_page - 1);
|
||||
});
|
||||
const toPage = computed(() => {
|
||||
let currentPage = props.data.current_page;
|
||||
let perPage = props.data.per_page;
|
||||
|
||||
if (props.data.current_page == props.data.last_page) {
|
||||
return props.data.total;
|
||||
} else {
|
||||
return currentPage * perPage;
|
||||
}
|
||||
});
|
||||
const fromPage = computed(() => {
|
||||
let currentPage = props.data.current_page;
|
||||
let perPage = props.data.per_page;
|
||||
return currentPage * perPage - (perPage - 1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- current_page:
|
||||
1
|
||||
first_page:
|
||||
1
|
||||
first_page_url:
|
||||
'/?page=1'
|
||||
last_page:
|
||||
3
|
||||
last_page_url:
|
||||
'/?page=3'
|
||||
next_page_url:
|
||||
'/?page=2'
|
||||
per_page:
|
||||
5
|
||||
previous_page_url:
|
||||
null
|
||||
total:
|
||||
15 -->
|
||||
|
||||
<template>
|
||||
<!-- <nav v-if="data.links.length > 3" -->
|
||||
<nav v-if="data.total > 3" role="navigation" aria-label="Pagination Navigation"
|
||||
class="flex items-center justify-between">
|
||||
<div class="flex justify-between flex-1 sm:hidden">
|
||||
<span v-if="data.current_page <= 1"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||
Previous
|
||||
</span>
|
||||
<Link v-else :href="data.previous_page_url"
|
||||
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Previous
|
||||
</Link>
|
||||
|
||||
<Link v-if="data.current_page < data.last_page" :href="data.next_page_url"
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
|
||||
Next
|
||||
</Link>
|
||||
<span v-else
|
||||
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md">
|
||||
Next
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700 leading-5">
|
||||
Showing
|
||||
<span class="font-medium">{{ fromPage }}</span>
|
||||
to
|
||||
<span class="font-medium">{{ toPage }}</span>
|
||||
of
|
||||
<span class="font-medium">{{ data.total }}</span>
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="relative z-0 inline-flex shadow-sm rounded-md">
|
||||
<span v-if="props.data.current_page <= 1" aria-disabled="true" aria-label="Previous">
|
||||
<span
|
||||
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
|
||||
aria-hidden="true">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- <Link v-else :href="data.previous_page_url" rel="prev" -->
|
||||
<Link v-else :href="prevPageLink" rel="prev"
|
||||
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||
aria-label="Previous">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
<!-- <template v-for="(link, key) in data.links">
|
||||
<template v-if="key > 0 && key < data.last_page + 1">
|
||||
<span v-if="!link.active && link.url === null" :key="key" aria-disabled="true">
|
||||
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
|
||||
</span>
|
||||
|
||||
<span v-else-if="link.active" :key="`current-${key}`" aria-current="page">
|
||||
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
|
||||
</span>
|
||||
|
||||
<Link v-else :key="`link-${key}`" :href="link.url" v-html="link.label" class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" aria-label="`Go to page ${link.label}`" />
|
||||
</template>
|
||||
</template> -->
|
||||
|
||||
<Link v-if="props.data.current_page < props.data.last_page" :href="nextPageLink" rel="next"
|
||||
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
|
||||
aria-label="Next">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</Link>
|
||||
<!-- else disabled link -->
|
||||
<span v-else aria-disabled="true" aria-label="Next">
|
||||
<span
|
||||
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
|
||||
aria-hidden="true">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
76
resources/js/Components/Admin/Sort.vue
Normal file
76
resources/js/Components/Admin/Sort.vue
Normal file
|
@ -0,0 +1,76 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
attribute: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
search: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const downFill = ref('lightgray');
|
||||
const upFill = ref('lightgray');
|
||||
|
||||
const sortLink = computed(() => {
|
||||
let url = new URL(document.location);
|
||||
let sortValue = url.searchParams.get('sort');
|
||||
|
||||
if (sortValue == props.attribute) {
|
||||
url.searchParams.set('sort', '-' + props.attribute);
|
||||
upFill.value = 'black';
|
||||
} else if (sortValue === '-' + props.attribute) {
|
||||
url.searchParams.set('sort', props.attribute);
|
||||
downFill.value = 'black';
|
||||
} else {
|
||||
url.searchParams.set('sort', props.attribute);
|
||||
}
|
||||
|
||||
if (props.search == '') {
|
||||
url.searchParams.delete('search');
|
||||
} else {
|
||||
url.searchParams.set('search', props.search);
|
||||
}
|
||||
|
||||
return url.href;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-4">
|
||||
<Link :href="sortLink" class="no-underline hover:underline text-cyan-600 dark:text-cyan-400">
|
||||
{{ label }}
|
||||
</Link>
|
||||
<div class="flex flex-col">
|
||||
<svg
|
||||
class="inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15px"
|
||||
height="15px"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M7.5 3L15 11H0L7.5 3Z" :fill="upFill" />
|
||||
</svg>
|
||||
<svg
|
||||
class="inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15px"
|
||||
height="15px"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M7.49988 12L-0.00012207 4L14.9999 4L7.49988 12Z" :fill="downFill" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
30
resources/js/Components/AsideMenu.vue
Normal file
30
resources/js/Components/AsideMenu.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup>
|
||||
// import { reactive, computed } from 'vue';
|
||||
// import { usePage } from '@inertiajs/vue3'
|
||||
// import { usePage } from '@inertiajs/inertia-vue3';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import menu from '@/menu.js'
|
||||
import AsideMenuLayer from '@/Components/AsideMenuLayer.vue';
|
||||
import OverlayLayer from '@/Components/OverlayLayer.vue';
|
||||
|
||||
// let menu = reactive({});
|
||||
// menu = computed(() => usePage().props.navigation?.menu);
|
||||
|
||||
const layoutService = LayoutService();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AsideMenuLayer
|
||||
v-if="menu && Object.keys(menu).length"
|
||||
:menu="menu"
|
||||
:class="[
|
||||
layoutService.isAsideMobileExpanded ? 'left-0' : '-left-60 lg:left-0',
|
||||
{ 'lg:hidden xl:flex': !layoutService.isAsideLgActive },
|
||||
]"
|
||||
/>
|
||||
<!-- <OverlayLayer
|
||||
v-show="layoutService.isAsideLgActive"
|
||||
z-index="z-30"
|
||||
@overlay-click="layoutService.isAsideLgActive = false"
|
||||
/> -->
|
||||
</template>
|
93
resources/js/Components/AsideMenuItem.vue
Normal file
93
resources/js/Components/AsideMenuItem.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
// import { Link } from '@inertiajs/inertia-vue3';
|
||||
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { mdiMinus, mdiPlus } from '@mdi/js';
|
||||
import { getButtonColor } from '@/colors.js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isDropdownList: Boolean,
|
||||
});
|
||||
|
||||
const itemRoute = computed(() => (props.item && props.item.route ? stardust.route(props.item.route): ''));
|
||||
// const isCurrentRoute = computed(() => (props.item && props.item.route ? stardust.isCurrent(props.item.route): false));
|
||||
const itemHref = computed(() => (props.item && props.item.href ? props.item.href : ''));
|
||||
|
||||
const emit = defineEmits(['menu-click']);
|
||||
|
||||
const styleService = StyleService();
|
||||
|
||||
const hasColor = computed(() => props.item && props.item.color);
|
||||
|
||||
const isDropdownActive = ref(false);
|
||||
|
||||
const componentClass = computed(() => [
|
||||
props.isDropdownList ? 'py-3 px-6 text-sm' : 'py-3 px-6',
|
||||
hasColor.value ? getButtonColor(props.item.color, false, true) : styleService.asideMenuItemStyle,
|
||||
]);
|
||||
|
||||
const hasDropdown = computed(() => props.item.children);
|
||||
|
||||
const menuClick = (event) => {
|
||||
emit('menu-click', event, props.item);
|
||||
|
||||
if (hasDropdown.value) {
|
||||
isDropdownActive.value = !isDropdownActive.value;
|
||||
}
|
||||
};
|
||||
|
||||
const activeInactiveStyle = computed(() => {
|
||||
if (props.item.route && stardust.isCurrent(props.item.route)) {
|
||||
// console.log(props.item.route);
|
||||
return styleService.asideMenuItemActiveStyle;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const is = computed(() => {
|
||||
if (props.item.href) {
|
||||
return 'a'
|
||||
}
|
||||
if (props.item.route) {
|
||||
return Link
|
||||
}
|
||||
|
||||
return 'div'
|
||||
})
|
||||
|
||||
// props.routeName && stardust.isCurrent(props.routeName) ? props.activeColor : null
|
||||
|
||||
</script>
|
||||
|
||||
<!-- :target="props.item.target ?? null" -->
|
||||
<template>
|
||||
<li>
|
||||
<!-- <component :is="itemHref ? 'div' : Link" :href="itemHref ? itemHref : itemRoute" -->
|
||||
<component :is="is" :href="itemRoute ? stardust.route(props.item.route) : props.item.href"
|
||||
class="flex cursor-pointer dark:text-slate-300 dark:hover:text-white" :class="componentClass"
|
||||
@click="menuClick" v-bind:target="props.item.target ?? null">
|
||||
<BaseIcon v-if="item.icon" :path="item.icon" class="flex-none" :class="activeInactiveStyle" w="w-16"
|
||||
:size="18" />
|
||||
<span class="grow text-ellipsis line-clamp-1" :class="activeInactiveStyle">
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<!-- <BaseIcon v-if="hasDropdown" :path="isDropdownActive ? mdiMinus : mdiPlus" class="flex-none"
|
||||
:class="activeInactiveStyle" w="w-12" /> -->
|
||||
</component>
|
||||
<!-- <AsideMenuList v-if="hasDropdown" :menu="item.children" :class="[
|
||||
styleService.asideMenuDropdownStyle,
|
||||
isDropdownActive ? 'block dark:bg-slate-800/50' : 'hidden',
|
||||
]" is-dropdown-list /> -->
|
||||
</li>
|
||||
</template>
|
66
resources/js/Components/AsideMenuLayer.vue
Normal file
66
resources/js/Components/AsideMenuLayer.vue
Normal file
|
@ -0,0 +1,66 @@
|
|||
<script setup>
|
||||
import { router } from '@inertiajs/vue3'
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import { mdiLogout, mdiClose } from '@mdi/js';
|
||||
import { computed } from 'vue';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import AsideMenuList from '@/Components/AsideMenuList.vue';
|
||||
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
defineProps({
|
||||
menu: {
|
||||
type: Object,
|
||||
default: () => { },
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['menu-click']);
|
||||
|
||||
const layoutStore = LayoutService();
|
||||
|
||||
const styleStore = StyleService();
|
||||
|
||||
const logoutItem = computed(() => ({
|
||||
name: 'Logout',
|
||||
label: 'Logout',
|
||||
icon: mdiLogout,
|
||||
color: 'info',
|
||||
link: '#',
|
||||
}));
|
||||
|
||||
const logoutItemClick = async () => {
|
||||
// router.post(route('logout'));
|
||||
await router.post(stardust.route('logout'));
|
||||
};
|
||||
|
||||
const menuClick = (event, item) => {
|
||||
emit('menu-click', event, item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside id="aside" class="lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden">
|
||||
<div :class="styleStore.asideStyle" class="lg:rounded-xl flex-1 flex flex-col overflow-hidden dark:bg-slate-900">
|
||||
<div :class="styleStore.asideBrandStyle"
|
||||
class="flex flex-row h-14 items-center justify-between dark:bg-slate-900">
|
||||
<div class="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
|
||||
<b class="font-black">Menu</b>
|
||||
</div>
|
||||
<button class="hidden lg:inline-block xl:hidden p-3" @click.prevent="layoutStore.isAsideLgActive = false">
|
||||
<BaseIcon :path="mdiClose" />
|
||||
</button>
|
||||
</div>
|
||||
<div :class="styleStore.darkMode ? 'aside-scrollbars-[slate]' : styleStore.asideScrollbarsStyle"
|
||||
class="flex-1 overflow-y-auto overflow-x-hidden">
|
||||
<AsideMenuList v-bind:menu="menu" @menu-click="menuClick" />
|
||||
</div>
|
||||
<!-- <p class="menu-label">About</p>> -->
|
||||
<ul class="menu-list">
|
||||
<AsideMenuItem :item="logoutItem" @menu-click="logoutItemClick" />
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
29
resources/js/Components/AsideMenuList.vue
Normal file
29
resources/js/Components/AsideMenuList.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script setup>
|
||||
import AsideMenuItem from '@/Components/AsideMenuItem.vue';
|
||||
|
||||
defineProps({
|
||||
isDropdownList: Boolean,
|
||||
menu: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['menu-click'])
|
||||
|
||||
const menuClick = (event, item) => {
|
||||
emit('menu-click', event, item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<AsideMenuItem
|
||||
v-for="(item, index) in menu"
|
||||
:key="index"
|
||||
v-bind:item="item"
|
||||
:is-dropdown-list="isDropdownList"
|
||||
@menu-click="menuClick"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
117
resources/js/Components/BaseButton.vue
Normal file
117
resources/js/Components/BaseButton.vue
Normal file
|
@ -0,0 +1,117 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
// import { Link } from '@inertiajs/inertia-vue3';
|
||||
import { getButtonColor } from '@/colors.js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'white',
|
||||
},
|
||||
as: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
small: Boolean,
|
||||
outline: Boolean,
|
||||
active: Boolean,
|
||||
disabled: Boolean,
|
||||
roundedFull: Boolean,
|
||||
});
|
||||
|
||||
const is = computed(() => {
|
||||
if (props.as) {
|
||||
return props.as;
|
||||
}
|
||||
|
||||
if (props.routeName) {
|
||||
return Link;
|
||||
}
|
||||
|
||||
if (props.href) {
|
||||
return 'a';
|
||||
}
|
||||
|
||||
return 'button';
|
||||
});
|
||||
|
||||
const computedType = computed(() => {
|
||||
if (is.value === 'button') {
|
||||
return props.type ?? 'button';
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const labelClass = computed(() => (props.small && props.icon ? 'px-1' : 'px-2'));
|
||||
|
||||
const componentClass = computed(() => {
|
||||
const base = [
|
||||
'inline-flex',
|
||||
'cursor-pointer',
|
||||
'justify-center',
|
||||
'items-center',
|
||||
'whitespace-nowrap',
|
||||
'focus:outline-none',
|
||||
'transition-colors',
|
||||
'focus:ring',
|
||||
'duration-150',
|
||||
'border',
|
||||
props.roundedFull ? 'rounded-full' : 'rounded',
|
||||
props.active ? 'ring ring-black dark:ring-white' : 'ring-blue-700',
|
||||
getButtonColor(props.color, props.outline, !props.disabled),
|
||||
];
|
||||
|
||||
if (props.small) {
|
||||
base.push('text-sm', props.roundedFull ? 'px-3 py-1' : 'p-1');
|
||||
} else {
|
||||
base.push('py-2', props.roundedFull ? 'px-6' : 'px-3');
|
||||
}
|
||||
|
||||
if (props.disabled) {
|
||||
base.push('cursor-not-allowed', props.outline ? 'opacity-50' : 'opacity-70');
|
||||
}
|
||||
|
||||
return base;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="is"
|
||||
:class="componentClass"
|
||||
:href="routeName ? routeName : href"
|
||||
:type="computedType"
|
||||
:target="target"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<BaseIcon v-if="icon" :path="icon" />
|
||||
<span v-if="label" :class="labelClass">{{ label }}</span>
|
||||
</component>
|
||||
</template>
|
55
resources/js/Components/BaseButtons.vue
Normal file
55
resources/js/Components/BaseButtons.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import { h, defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseButtons',
|
||||
props: {
|
||||
noWrap: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'justify-start'
|
||||
},
|
||||
classAddon: {
|
||||
type: String,
|
||||
default: 'mr-3 last:mr-0 mb-3'
|
||||
},
|
||||
mb: {
|
||||
type: String,
|
||||
default: '-mb-3'
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const hasSlot = this.$slots && this.$slots.default
|
||||
|
||||
const parentClass = [
|
||||
'flex',
|
||||
'items-center',
|
||||
this.type,
|
||||
this.noWrap ? 'flex-nowrap' : 'flex-wrap'
|
||||
]
|
||||
|
||||
if (this.mb) {
|
||||
parentClass.push(this.mb)
|
||||
}
|
||||
|
||||
return h(
|
||||
'div',
|
||||
{ class: parentClass },
|
||||
hasSlot
|
||||
? this.$slots.default().map(element => {
|
||||
if (element && element.children && typeof element.children === 'object') {
|
||||
return h(
|
||||
element,
|
||||
{},
|
||||
element.children.map(child => {
|
||||
return h(child, { class: [this.classAddon] })
|
||||
}))
|
||||
}
|
||||
|
||||
return h(element, { class: [this.classAddon] })
|
||||
})
|
||||
: null
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
12
resources/js/Components/BaseDivider.vue
Normal file
12
resources/js/Components/BaseDivider.vue
Normal file
|
@ -0,0 +1,12 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
navBar: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hr
|
||||
:class="props.navBar ? 'hidden lg:block lg:my-0.5 dark:border-slate-700' : 'my-6 -mx-6 dark:border-slate-800'"
|
||||
class="border-t border-gray-100"
|
||||
>
|
||||
</template>
|
40
resources/js/Components/BaseIcon.vue
Normal file
40
resources/js/Components/BaseIcon.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
path: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
w: {
|
||||
type: String,
|
||||
default: 'w-6'
|
||||
},
|
||||
h: {
|
||||
type: String,
|
||||
default: 'h-6'
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 16
|
||||
}
|
||||
})
|
||||
|
||||
const spanClass = computed(() => `inline-flex justify-center items-center ${props.w} ${props.h}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="spanClass">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
:width="size"
|
||||
:height="size"
|
||||
class="inline-block"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
:d="path"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
37
resources/js/Components/BaseLevel.vue
Normal file
37
resources/js/Components/BaseLevel.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<script>
|
||||
import { h, defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseLevel',
|
||||
props: {
|
||||
mobile: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'justify-between'
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const parentClass = [this.type, 'items-center']
|
||||
|
||||
const parentMobileClass = ['flex']
|
||||
|
||||
const parentBaseClass = ['block', 'md:flex']
|
||||
|
||||
const childBaseClass = ['flex', 'shrink-0', 'grow-0', 'items-center', 'justify-center']
|
||||
|
||||
return h(
|
||||
'div',
|
||||
{ class: parentClass.concat(this.mobile ? parentMobileClass : parentBaseClass) },
|
||||
this.$slots.default().map((element, index) => {
|
||||
const childClass = (!this.mobile && this.$slots.default().length > index + 1)
|
||||
? childBaseClass.concat(['mb-6', 'md:mb-0'])
|
||||
: childBaseClass
|
||||
|
||||
return h('div', { class: childClass }, [
|
||||
element
|
||||
])
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
111
resources/js/Components/CardBox.vue
Normal file
111
resources/js/Components/CardBox.vue
Normal file
|
@ -0,0 +1,111 @@
|
|||
<script setup>
|
||||
import { mdiCog } from '@mdi/js'
|
||||
import { computed, useSlots } from 'vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
rounded: {
|
||||
type: String,
|
||||
default: 'rounded-xl'
|
||||
},
|
||||
hasTable: Boolean,
|
||||
empty: Boolean,
|
||||
form: Boolean,
|
||||
hoverable: Boolean,
|
||||
modal: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['header-icon-click', 'submit'])
|
||||
|
||||
const is = computed(() => props.form ? 'form' : 'div')
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const footer = computed(() => slots.footer && !!slots.footer())
|
||||
|
||||
const componentClass = computed(() => {
|
||||
const base = [
|
||||
props.rounded,
|
||||
props.modal ? 'dark:bg-slate-900' : 'dark:bg-slate-900/70'
|
||||
]
|
||||
|
||||
if (props.hoverable) {
|
||||
base.push('hover:shadow-lg transition-shadow duration-500')
|
||||
}
|
||||
|
||||
return base
|
||||
})
|
||||
|
||||
const computedHeaderIcon = computed(() => props.headerIcon ?? mdiCog)
|
||||
|
||||
const headerIconClick = () => {
|
||||
emit('header-icon-click')
|
||||
}
|
||||
|
||||
const submit = e => {
|
||||
emit('submit', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="is"
|
||||
:class="componentClass"
|
||||
class="bg-white flex flex-col"
|
||||
@submit="submit"
|
||||
>
|
||||
<header
|
||||
v-if="title"
|
||||
class="flex items-stretch border-b border-gray-100 dark:border-slate-800"
|
||||
>
|
||||
<div
|
||||
class="flex items-center py-3 grow font-bold"
|
||||
:class="[ icon ? 'px-4' : 'px-6' ]"
|
||||
>
|
||||
<BaseIcon
|
||||
v-if="icon"
|
||||
:path="icon"
|
||||
class="mr-3"
|
||||
/>
|
||||
{{ title }}
|
||||
</div>
|
||||
<button
|
||||
class="flex items-center py-3 px-4 justify-center ring-blue-700 focus:ring"
|
||||
@click="headerIconClick"
|
||||
>
|
||||
<BaseIcon :path="computedHeaderIcon" />
|
||||
</button>
|
||||
</header>
|
||||
<div
|
||||
v-if="empty"
|
||||
class="text-center py-24 text-gray-500 dark:text-slate-400"
|
||||
>
|
||||
<p>Nothing's here…</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex-1"
|
||||
:class="{'p-6':!hasTable}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
v-if="footer"
|
||||
class="p-6 border-t border-gray-100 dark:border-slate-800"
|
||||
>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
101
resources/js/Components/CardBoxClient.vue
Normal file
101
resources/js/Components/CardBoxClient.vue
Normal file
|
@ -0,0 +1,101 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { mdiTrendingDown, mdiTrendingUp, mdiTrendingNeutral } from '@mdi/js'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import PillTag from '@/Components/PillTag.vue'
|
||||
import UserAvatar from '@/Components/UserAvatar.vue'
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
login: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const pillType = computed(() => {
|
||||
if (props.type) {
|
||||
return props.type
|
||||
}
|
||||
|
||||
if (props.progress) {
|
||||
if (props.progress >= 60) {
|
||||
return 'success'
|
||||
}
|
||||
if (props.progress >= 40) {
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
return 'danger'
|
||||
}
|
||||
|
||||
return 'info'
|
||||
})
|
||||
|
||||
const pillIcon = computed(() => {
|
||||
return {
|
||||
success: mdiTrendingUp,
|
||||
warning: mdiTrendingNeutral,
|
||||
danger: mdiTrendingDown,
|
||||
info: mdiTrendingNeutral,
|
||||
}[pillType.value]
|
||||
})
|
||||
|
||||
const pillText = computed(() => props.text ?? `${props.progress}%`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox
|
||||
class="mb-6 last:mb-0"
|
||||
hoverable
|
||||
>
|
||||
<BaseLevel>
|
||||
<BaseLevel type="justify-start">
|
||||
<UserAvatar
|
||||
class="w-12 h-12 mr-6"
|
||||
:username="props.name"
|
||||
/>
|
||||
<div class="text-center md:text-left overflow-hidden">
|
||||
<h4 class="text-xl text-ellipsis ">
|
||||
{{ name }}
|
||||
</h4>
|
||||
<p class="text-gray-500 dark:text-slate-400">
|
||||
<!-- {{ date }} @ {{ login }} -->
|
||||
{{ email }}
|
||||
</p>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
<PillTag
|
||||
:type="pillType"
|
||||
:text="pillText"
|
||||
small
|
||||
:icon="pillIcon"
|
||||
/>
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</template>
|
69
resources/js/Components/CardBoxModal.vue
Normal file
69
resources/js/Components/CardBoxModal.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { mdiClose } from '@mdi/js'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import OverlayLayer from '@/Components/OverlayLayer.vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
largeTitle: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
button: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
},
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
default: 'Done'
|
||||
},
|
||||
hasCancel: Boolean,
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean],
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm'])
|
||||
|
||||
const value = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
const confirmCancel = (mode) => {
|
||||
value.value = false;
|
||||
emit(mode);
|
||||
}
|
||||
|
||||
const confirm = () => confirmCancel('confirm')
|
||||
|
||||
const cancel = () => confirmCancel('cancel')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OverlayLayer v-show="value" @overlay-click="cancel">
|
||||
<CardBox v-show="value" :title="title" class="shadow-lg max-h-modal w-11/12 md:w-3/5 lg:w-2/5 xl:w-4/12 z-50"
|
||||
:header-icon="mdiClose" modal @header-icon-click="cancel">
|
||||
<div class="space-y-3">
|
||||
<h1 v-if="largeTitle" class="text-2xl">
|
||||
{{ largeTitle }}
|
||||
</h1>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton :label="buttonLabel" :color="button" @click="confirm" />
|
||||
<BaseButton v-if="hasCancel" label="Cancel" :color="button" outline @click="cancel" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</OverlayLayer>
|
||||
</template>
|
96
resources/js/Components/CardBoxTransaction.vue
Normal file
96
resources/js/Components/CardBoxTransaction.vue
Normal file
|
@ -0,0 +1,96 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { mdiCashMinus, mdiCashPlus, mdiReceipt, mdiCreditCardOutline } from '@mdi/js'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import PillTag from '@/Components/PillTag.vue'
|
||||
import IconRounded from '@/Components/IconRounded.vue'
|
||||
|
||||
const props = defineProps({
|
||||
amount: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
business: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
account: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const icon = computed(() => {
|
||||
if (props.type === 'withdrawal') {
|
||||
return {
|
||||
icon: mdiCashMinus,
|
||||
type: 'danger'
|
||||
}
|
||||
} else if (props.type === 'deposit') {
|
||||
return {
|
||||
icon: mdiCashPlus,
|
||||
type: 'success'
|
||||
}
|
||||
} else if (props.type === 'invoice') {
|
||||
return {
|
||||
icon: mdiReceipt,
|
||||
type: 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
icon: mdiCreditCardOutline,
|
||||
type: 'info'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox
|
||||
class="mb-6 last:mb-0"
|
||||
hoverable
|
||||
>
|
||||
<BaseLevel>
|
||||
<BaseLevel type="justify-start">
|
||||
<IconRounded
|
||||
:icon="icon.icon"
|
||||
:type="icon.type"
|
||||
class="md:mr-6"
|
||||
/>
|
||||
<div class="text-center space-y-1 md:text-left md:mr-6">
|
||||
<h4 class="text-xl">
|
||||
${{ amount }}
|
||||
</h4>
|
||||
<p class="text-gray-500 dark:text-slate-400">
|
||||
<b>{{ date }}</b> via {{ business }}
|
||||
</p>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
<div class="text-center md:text-right space-y-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
{{ name }}
|
||||
</p>
|
||||
<div>
|
||||
<PillTag
|
||||
:type="icon.type"
|
||||
:text="type"
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</template>
|
64
resources/js/Components/CardBoxWidget.vue
Normal file
64
resources/js/Components/CardBoxWidget.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<script setup>
|
||||
import { mdiCog } from '@mdi/js'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import NumberDynamic from '@/Components/NumberDynamic.vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import PillTagTrend from '@/Components/PillTagTrend.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
|
||||
defineProps({
|
||||
number: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
trend: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
trendType: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox>
|
||||
<BaseLevel v-if="trend" class="mb-3" mobile>
|
||||
<PillTagTrend :trend="trend" :trend-type="trendType" small />
|
||||
<BaseButton :icon="mdiCog" icon-w="w-4" icon-h="h-4" color="white" small />
|
||||
</BaseLevel>
|
||||
<BaseLevel mobile>
|
||||
<div>
|
||||
<h3 class="text-lg leading-tight text-gray-500 dark:text-slate-400">
|
||||
{{ label }}
|
||||
</h3>
|
||||
<h1 class="text-3xl leading-tight font-semibold">
|
||||
<NumberDynamic :value="number" :prefix="prefix" :suffix="suffix" />
|
||||
</h1>
|
||||
</div>
|
||||
<BaseIcon v-if="icon" :path="icon" size="48" w="" h="h-16" :class="color" />
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</template>
|
62
resources/js/Components/Charts/LineChart.vue
Normal file
62
resources/js/Components/Charts/LineChart.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<script setup>
|
||||
import { ref, watch, computed, onMounted } from 'vue'
|
||||
import {
|
||||
Chart,
|
||||
LineElement,
|
||||
PointElement,
|
||||
LineController,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
Tooltip
|
||||
} from 'chart.js'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const root = ref(null)
|
||||
|
||||
let chart
|
||||
|
||||
Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip)
|
||||
|
||||
onMounted(() => {
|
||||
chart = new Chart(root.value, {
|
||||
type: 'line',
|
||||
data: props.data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
display: false
|
||||
},
|
||||
x: {
|
||||
display: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const chartData = computed(() => props.data)
|
||||
|
||||
watch(chartData, data => {
|
||||
if (chart) {
|
||||
chart.data = data
|
||||
chart.update()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="root" />
|
||||
</template>
|
54
resources/js/Components/Charts/chart.config.js
Normal file
54
resources/js/Components/Charts/chart.config.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
export const chartColors = {
|
||||
default: {
|
||||
primary: '#00D1B2',
|
||||
info: '#209CEE',
|
||||
danger: '#FF3860'
|
||||
}
|
||||
}
|
||||
|
||||
const randomChartData = n => {
|
||||
const data = []
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
data.push(Math.round(Math.random() * 200))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const datasetObject = (color, points) => {
|
||||
return {
|
||||
fill: false,
|
||||
borderColor: chartColors.default[color],
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: chartColors.default[color],
|
||||
pointBorderColor: 'rgba(255,255,255,0)',
|
||||
pointHoverBackgroundColor: chartColors.default[color],
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: randomChartData(points),
|
||||
tension: 0.5,
|
||||
cubicInterpolationMode: 'default'
|
||||
}
|
||||
}
|
||||
|
||||
export const sampleChartData = (points = 9) => {
|
||||
const labels = []
|
||||
|
||||
for (let i = 1; i <= points; i++) {
|
||||
labels.push(`0${i}`)
|
||||
}
|
||||
|
||||
return {
|
||||
labels,
|
||||
datasets: [
|
||||
datasetObject('primary', points),
|
||||
datasetObject('info', points),
|
||||
datasetObject('danger', points)
|
||||
]
|
||||
}
|
||||
}
|
26
resources/js/Components/FooterBar.vue
Normal file
26
resources/js/Components/FooterBar.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { containerMaxW } from '@/config.js';
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import JustboilLogo from '@/Components/JustboilLogo.vue'
|
||||
|
||||
const year = computed(() => new Date().getFullYear())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="py-2 px-6">
|
||||
<BaseLevel :class="containerMaxW">
|
||||
<div class="text-center md:text-left">
|
||||
<b>©{{ year }}, <a href="https://tethys.at/" target="_blank">
|
||||
Tethys.at</a>.</b>
|
||||
<!-- Get more with <a href="https://tailwind-vue.justboil.me/" target="_blank" class="text-blue-600">Premium
|
||||
version</a> -->
|
||||
</div>
|
||||
<div class="md:py-3">
|
||||
<a href="https://www.tethys.at" target="_blank">
|
||||
<JustboilLogo class="w-auto h-8 md:h-6" />
|
||||
</a>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
</footer>
|
||||
</template>
|
51
resources/js/Components/FormCheckRadio.vue
Normal file
51
resources/js/Components/FormCheckRadio.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'checkbox',
|
||||
validator: value => ['checkbox', 'radio', 'switch'].includes(value)
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String, Number, Boolean],
|
||||
default: null
|
||||
},
|
||||
inputValue: {
|
||||
type: [String, Number, Boolean],
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const computedValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
const inputType = computed(() => props.type === 'radio' ? 'radio' : 'checkbox')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
:class="type"
|
||||
class="mr-6 mb-3 last:mr-0"
|
||||
>
|
||||
<input
|
||||
v-model="computedValue"
|
||||
:type="inputType"
|
||||
:name="name"
|
||||
:value="inputValue"
|
||||
>
|
||||
<span class="check" />
|
||||
<span class="pl-2">{{ label }}</span>
|
||||
</label>
|
||||
</template>
|
57
resources/js/Components/FormCheckRadioGroup.vue
Normal file
57
resources/js/Components/FormCheckRadioGroup.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import FormCheckRadio from '@/Components/FormCheckRadio.vue'
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'checkbox',
|
||||
validator: value => ['checkbox', 'radio', 'switch'].includes(value)
|
||||
},
|
||||
componentClass: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
isColumn: Boolean,
|
||||
modelValue: {
|
||||
type: [Array, String, Number, Boolean, Object],
|
||||
default: null
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const computedValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-start flex-wrap -mb-3"
|
||||
:class="{ 'flex-col': isColumn }"
|
||||
>
|
||||
<!-- :input-value="key" -->
|
||||
<!-- :label="value" -->
|
||||
<!-- :input-value="value.id"
|
||||
:label="value.name" -->
|
||||
<FormCheckRadio
|
||||
v-for="(value, key) in options"
|
||||
:key="key"
|
||||
v-model="computedValue"
|
||||
:type="type"
|
||||
:name="name"
|
||||
:input-value="key"
|
||||
:label="value"
|
||||
:class="componentClass"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
144
resources/js/Components/FormControl.vue
Normal file
144
resources/js/Components/FormControl.vue
Normal file
|
@ -0,0 +1,144 @@
|
|||
<script setup>
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import FormControlIcon from '@/Components/FormControlIcon.vue';
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
inputmode: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Array, Object],
|
||||
default: '',
|
||||
},
|
||||
required: Boolean,
|
||||
borderless: Boolean,
|
||||
transparent: Boolean,
|
||||
ctrlKFocus: Boolean,
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'setRef']);
|
||||
const computedValue = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
const inputElClass = computed(() => {
|
||||
const base = [
|
||||
'px-3 py-2 max-w-full focus:ring focus:outline-none border-gray-700 rounded w-full',
|
||||
'dark:placeholder-gray-400',
|
||||
computedType.value === 'textarea' ? 'h-24' : 'h-12',
|
||||
props.borderless ? 'border-0' : 'border',
|
||||
props.transparent ? 'bg-transparent' : 'bg-white dark:bg-slate-800',
|
||||
];
|
||||
if (props.icon) {
|
||||
base.push('pl-10');
|
||||
}
|
||||
return base;
|
||||
});
|
||||
const computedType = computed(() => (props.options ? 'select' : props.type));
|
||||
const controlIconH = computed(() => (props.type === 'textarea' ? 'h-full' : 'h-12'));
|
||||
const mainService = MainService();
|
||||
const selectEl = ref(null);
|
||||
const textareaEl = ref(null);
|
||||
const inputEl = ref(null);
|
||||
onMounted(() => {
|
||||
if (selectEl.value) {
|
||||
emit('setRef', selectEl.value);
|
||||
} else if (textareaEl.value) {
|
||||
emit('setRef', textareaEl.value);
|
||||
} else {
|
||||
emit('setRef', inputEl.value);
|
||||
}
|
||||
});
|
||||
if (props.ctrlKFocus) {
|
||||
const fieldFocusHook = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
inputEl.value.focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
inputEl.value.blur();
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
if (!mainService.isFieldFocusRegistered) {
|
||||
window.addEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = true;
|
||||
} else {
|
||||
// console.error('Duplicate field focus event')
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', fieldFocusHook);
|
||||
mainService.isFieldFocusRegistered = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<select
|
||||
v-if="computedType === 'select'"
|
||||
:id="id"
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:class="inputElClass"
|
||||
>
|
||||
<option v-for="option in options" :key="option.id ?? option" :value="option">
|
||||
{{ option.label ?? option }}
|
||||
</option>
|
||||
</select>
|
||||
<textarea
|
||||
v-else-if="computedType === 'textarea'"
|
||||
:id="id"
|
||||
v-model="computedValue"
|
||||
:class="inputElClass"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
:id="id"
|
||||
ref="inputEl"
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:inputmode="inputmode"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:type="computedType"
|
||||
:class="inputElClass"
|
||||
/>
|
||||
<FormControlIcon v-if="icon" :icon="icon" :h="controlIconH" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
23
resources/js/Components/FormControlIcon.vue
Normal file
23
resources/js/Components/FormControlIcon.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script setup>
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
h: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseIcon
|
||||
:path="icon"
|
||||
w="w-10"
|
||||
:h="h"
|
||||
class="absolute top-0 left-0 z-10 pointer-events-none text-gray-500 dark:text-slate-400"
|
||||
/>
|
||||
</template>
|
47
resources/js/Components/FormField.vue
Normal file
47
resources/js/Components/FormField.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<script setup>
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
labelFor: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const wrapperClass = computed(() => {
|
||||
const base = [];
|
||||
const slotsLength = slots.default().length;
|
||||
|
||||
if (slotsLength > 1) {
|
||||
base.push('grid grid-cols-1 gap-3');
|
||||
}
|
||||
|
||||
if (slotsLength === 2) {
|
||||
base.push('md:grid-cols-2');
|
||||
}
|
||||
|
||||
return base;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-6 last:mb-0">
|
||||
<label v-if="label" :for="labelFor" class="block font-bold mb-2">{{ label }}</label>
|
||||
<div v-bind:class="wrapperClass">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="help" class="text-xs text-gray-500 dark:text-slate-400 mt-1">
|
||||
{{ help }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
108
resources/js/Components/FormFilePicker.vue
Normal file
108
resources/js/Components/FormFilePicker.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<script setup>
|
||||
import { mdiUpload } from '@mdi/js'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Object, File, Array],
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: 'Upload'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: mdiUpload
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const root = ref(null)
|
||||
|
||||
const file = ref(props.modelValue)
|
||||
|
||||
const modelValueProp = computed(() => props.modelValue)
|
||||
|
||||
watch(modelValueProp, value => {
|
||||
file.value = value
|
||||
|
||||
if (!value) {
|
||||
root.value.input.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const upload = event => {
|
||||
const value = event.target.files || event.dataTransfer.files
|
||||
|
||||
file.value = value[0]
|
||||
|
||||
emit('update:modelValue', file.value)
|
||||
|
||||
// Use this as an example for handling file uploads
|
||||
// let formData = new FormData()
|
||||
// formData.append('file', file.value)
|
||||
|
||||
// const mediaStoreRoute = `/your-route/`
|
||||
|
||||
// axios
|
||||
// .post(mediaStoreRoute, formData, {
|
||||
// headers: {
|
||||
// 'Content-Type': 'multipart/form-data'
|
||||
// },
|
||||
// onUploadProgress: progressEvent
|
||||
// })
|
||||
// .then(r => {
|
||||
//
|
||||
// })
|
||||
// .catch(err => {
|
||||
//
|
||||
// })
|
||||
}
|
||||
|
||||
// const uploadPercent = ref(0)
|
||||
//
|
||||
// const progressEvent = progressEvent => {
|
||||
// uploadPercent.value = Math.round(
|
||||
// (progressEvent.loaded * 100) / progressEvent.total
|
||||
// )
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-stretch justify-start relative">
|
||||
<label class="inline-flex">
|
||||
<BaseButton
|
||||
as="a"
|
||||
:label="label"
|
||||
:icon="icon"
|
||||
:color="color"
|
||||
:class="{ 'rounded-r-none': file }"
|
||||
/>
|
||||
<input
|
||||
ref="root"
|
||||
type="file"
|
||||
class="absolute top-0 left-0 w-full h-full opacity-0 outline-none cursor-pointer -z-1"
|
||||
:accept="accept"
|
||||
@input="upload"
|
||||
>
|
||||
</label>
|
||||
<div v-if="file">
|
||||
<span
|
||||
class="inline-flex px-4 py-2 justify-center bg-gray-100 dark:bg-slate-800 border-gray-200 dark:border-slate-700 border rounded-r"
|
||||
>
|
||||
{{ file.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
42
resources/js/Components/FormInput.vue
Normal file
42
resources/js/Components/FormInput.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label>
|
||||
<span v-if="label" class="block font-semibold">{{ label }}</span>
|
||||
<n-input v-bind:type="type" v-model:value="internalValue" v-bind:placeholder="placeholder"/>
|
||||
</label>
|
||||
<div v-if="Array.isArray(errors)" class="text-xs text-red-500">
|
||||
{{ errors.join(', ') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { NInput } from 'naive-ui';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
label: String,
|
||||
placeholder: String,
|
||||
modelValue: String,
|
||||
errors: Array,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const internalValue = computed({
|
||||
// get() {
|
||||
// return props.modelValue;
|
||||
// },
|
||||
// set(value) {
|
||||
// emit('update:modelValue', value);
|
||||
// },
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
</script>
|
22
resources/js/Components/FormValidationErrors.vue
Normal file
22
resources/js/Components/FormValidationErrors.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup>
|
||||
import { computed, defineProps } from 'vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import NotificationBarInCard from '@/Components/NotificationBarInCard.vue';
|
||||
|
||||
// const errors = computed(() => usePage().props.errors);
|
||||
// const hasErrors = computed(() => Object.keys(props.errors.value).length > 0);
|
||||
|
||||
const props = defineProps({
|
||||
errors: Object,
|
||||
});
|
||||
const hasErrors = computed(() =>{
|
||||
return props.errors != null && Object.keys(props.errors).length > 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NotificationBarInCard v-if="hasErrors" color="danger">
|
||||
<b>Whoops! Something went wrong.</b>
|
||||
<span v-for="(error, key) in errors" :key="key">{{ error }}</span>
|
||||
</NotificationBarInCard>
|
||||
</template>
|
32
resources/js/Components/Header.vue
Normal file
32
resources/js/Components/Header.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="bg-slate-100 py-3 px-6 flex justify-between items-center mb-6">
|
||||
|
||||
<div class="flex items-center space-x-6">
|
||||
<h3 class="font-bold">AdonisJS InertiaJS Example</h3>
|
||||
<nav class="flex items-center text-sm">
|
||||
<Link href="/app">Home</Link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-6 text-sm">
|
||||
<Link href="/app/login">Login</Link>
|
||||
<Link href="/app/register">Register</Link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
// import DefaultLayout from '../Layouts/Default.vue';
|
||||
|
||||
export default {
|
||||
// layout: DefaultLayout,
|
||||
props: {
|
||||
testing: String,
|
||||
},
|
||||
|
||||
components: {
|
||||
Link
|
||||
}
|
||||
}
|
||||
</script>
|
35
resources/js/Components/IconRounded.vue
Normal file
35
resources/js/Components/IconRounded.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<script setup>
|
||||
import { colorsText, colorsBgLight } from '@/colors.js';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
w: {
|
||||
type: String,
|
||||
default: 'w-12',
|
||||
},
|
||||
h: {
|
||||
type: String,
|
||||
default: 'h-12',
|
||||
},
|
||||
bg: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseIcon
|
||||
:path="icon"
|
||||
:w="w"
|
||||
:h="h"
|
||||
size="24"
|
||||
class="rounded-full"
|
||||
:class="bg ? colorsBgLight[type] : [colorsText[type], 'bg-gray-50 dark:bg-slate-800']"
|
||||
/>
|
||||
</template>
|
145
resources/js/Components/JustboilLogo.vue
Normal file
145
resources/js/Components/JustboilLogo.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 626.4 224.7" style="enable-background:new 0 0 626.4 224.7;" xml:space="preserve" fill="currentColor">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0"
|
||||
d="M215.3,69.1l-4.4,19.2h-47.1l-4.2,18h43.7l-4.5,19.2h-43.6l-5.9,25.4h47L192,170h-73.1l23.3-100.9H215.3z" />
|
||||
<path class="st0" d="M311.6,88.6h-31.4L261.4,170h-26.1L254,88.6h-31.5l4.5-19.5h89L311.6,88.6z" />
|
||||
<path class="st0" d="M422.1,69.1L398.9,170h-26.1l10.3-44.6h-38.3L334.4,170h-26.1l23.3-100.9h26.1l-8.6,37.1h38.3l8.6-37.1H422.1
|
||||
z" />
|
||||
<path class="st0" d="M536.7,69.1l-51.4,61.9l-9.1,39h-26.2l8.8-37.8l-23.2-63.1h27.9l13,39.6l30.2-39.6H536.7z" />
|
||||
<path class="st0" d="M563.8,171.8c-8.6,0-16.3-0.7-23.1-2.2c-6.8-1.5-12.6-3.3-17.5-5.6l5.2-24.2h2.8c4.7,4.2,10.1,7.5,16.4,9.9
|
||||
c6.3,2.3,13,3.5,20.3,3.5c7.4,0,12.9-1,16.3-3c3.5-2,5.2-4.9,5.2-8.6c0-1.4-0.3-2.6-0.8-3.6c-0.6-1-1.7-2-3.3-3
|
||||
c-1.6-1-3.9-2-6.7-2.9c-2.8-1-6.5-2.1-10.9-3.4c-4.9-1.4-9.3-2.8-13.2-4.4c-3.9-1.6-7.2-3.5-10-5.6c-2.8-2.2-4.8-4.8-6.3-7.7
|
||||
c-1.4-2.9-2.1-6.3-2.1-10.4c0-10.1,4.5-18.2,13.4-24.2c8.9-6,21.1-9,36.6-9c7.5,0,14.5,0.6,21.1,1.8c6.6,1.2,12.2,2.9,16.8,4.9
|
||||
l-4.9,23.2h-2.8c-3.5-3.2-8.2-5.9-13.9-8.1c-5.8-2.2-12.1-3.3-18.9-3.3c-6.7,0-11.9,1-15.5,2.9c-3.7,1.9-5.5,4.6-5.5,8
|
||||
c0,1.6,0.3,2.9,0.8,3.9c0.5,1,1.6,2.1,3.3,3c1.4,0.9,3.7,1.9,6.8,2.9c3.1,1.1,6.8,2.2,11,3.3c11.3,3,19.4,6.6,24.2,10.6
|
||||
s7.2,9.6,7.2,16.5c0,5.8-1.3,11-3.9,15.4c-2.6,4.4-6.2,8-10.8,10.8c-4.8,2.9-10.3,5.1-16.5,6.4
|
||||
C578.5,171.1,571.6,171.8,563.8,171.8z" />
|
||||
</g>
|
||||
<path class="st1" d="M539.2,37.1" />
|
||||
<path class="st1" d="M539.2,37.1" />
|
||||
<polygon class="st2" points="24,213.8 57.7,213.8 98.2,44.6 12.5,45.1 3.7,81 56.7,80.6 " />
|
||||
<polygon class="st0" points="72.1,170.5 112.4,2.8 199.8,2.8 192.4,39.2 137.1,39.2 105.8,170.1 " />
|
||||
<polygon class="st0" points="207,2.6 225.9,2.6 218.6,39 199.9,39 " />
|
||||
<polygon class="st3" points="233.2,2.9 252.1,2.9 244.7,39.4 226.1,39.4 " />
|
||||
<polygon class="st4" points="259.2,2.9 278.1,2.9 270.8,39.4 252.1,39.4 " />
|
||||
<g>
|
||||
<path class="st5" d="M97.4,213.7h-4.7l-8-12.1h-5.8l-2.8,12.1H72l7-30.2h8.3c1.9,0,3.4,0.1,4.5,0.4c1.2,0.2,2.2,0.7,3,1.3
|
||||
c0.8,0.6,1.4,1.2,1.8,2c0.4,0.8,0.7,1.8,0.7,2.9c0,2.5-0.8,4.7-2.4,6.6c-1.6,1.9-3.7,3.2-6.2,4L97.4,213.7z M92.9,191
|
||||
c0-0.7-0.1-1.3-0.3-1.8c-0.2-0.5-0.6-0.9-1-1.3c-0.5-0.4-1.2-0.7-1.9-0.9c-0.7-0.2-1.7-0.2-2.8-0.2h-4.6l-2.7,11.6h4.3
|
||||
c1.3,0,2.5-0.1,3.5-0.4c1-0.2,1.9-0.7,2.7-1.3c0.9-0.7,1.6-1.5,2.1-2.5C92.6,193.2,92.9,192.1,92.9,191z" />
|
||||
<path class="st5" d="M111.5,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7c2.6-3,5.9-4.5,9.8-4.5
|
||||
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
|
||||
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9s2.9-1.2,4-2h0.2l-0.8,4.1
|
||||
c-0.7,0.2-1.3,0.5-1.8,0.7c-0.5,0.2-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C113.3,214.2,112.5,214.2,111.5,214.2z M119.2,199.9
|
||||
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
|
||||
c-1.5,1.2-2.5,2.8-3,4.7H119.2z" />
|
||||
<path class="st5" d="M132.8,214.3c-1.7,0-3.2-0.2-4.6-0.6s-2.5-0.9-3.4-1.4l0.9-4.1h0.2c0.3,0.2,0.7,0.5,1.1,0.9
|
||||
c0.5,0.3,1,0.7,1.7,1c0.6,0.3,1.4,0.6,2.2,0.8c0.8,0.2,1.7,0.3,2.6,0.3c1.9,0,3.4-0.4,4.5-1.1c1.1-0.7,1.6-1.7,1.6-3.1
|
||||
c0-0.7-0.3-1.3-0.8-1.7c-0.6-0.4-1.4-0.7-2.4-0.9c-0.5-0.1-1.2-0.3-1.9-0.4c-0.7-0.1-1.5-0.3-2.3-0.5c-1.6-0.4-2.8-1.1-3.5-1.9
|
||||
c-0.8-0.8-1.1-1.9-1.1-3.1c0-1.1,0.2-2,0.7-3c0.5-0.9,1.1-1.8,2.1-2.5c0.9-0.7,2-1.3,3.3-1.8c1.3-0.5,2.8-0.7,4.5-0.7
|
||||
c1.4,0,2.7,0.2,4.1,0.5c1.4,0.3,2.5,0.8,3.3,1.3l-0.8,3.9h-0.2c-0.2-0.2-0.5-0.4-1-0.7c-0.4-0.3-1-0.6-1.7-0.9
|
||||
c-0.6-0.3-1.3-0.5-2.1-0.7c-0.8-0.2-1.6-0.3-2.4-0.3c-1.7,0-3.1,0.4-4.1,1.1s-1.6,1.7-1.6,2.9c0,0.7,0.3,1.2,0.8,1.7
|
||||
c0.5,0.5,1.3,0.8,2.4,1.1c0.7,0.2,1.4,0.3,2.1,0.5c0.7,0.1,1.4,0.3,2.1,0.5c1.6,0.4,2.8,1,3.6,1.8s1.2,1.9,1.2,3.1
|
||||
c0,1-0.2,2.1-0.7,3.1c-0.5,1-1.2,1.9-2.2,2.6c-1,0.8-2.1,1.4-3.5,1.8C136,214,134.5,214.3,132.8,214.3z" />
|
||||
<path class="st5" d="M158.2,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7c2.6-3,5.9-4.5,9.8-4.5
|
||||
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
|
||||
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9c1.6-0.6,2.9-1.2,4-2h0.2l-0.8,4.1
|
||||
c-0.7,0.2-1.3,0.5-1.8,0.7c-0.5,0.2-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C160,214.2,159.2,214.2,158.2,214.2z M165.9,199.9
|
||||
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
|
||||
c-1.5,1.2-2.5,2.8-3,4.7H165.9z" />
|
||||
<path class="st5"
|
||||
d="M186.8,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
|
||||
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
|
||||
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
|
||||
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1H177
|
||||
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
|
||||
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L186.8,211.3z M189.1,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
|
||||
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L189.1,201.8z" />
|
||||
<path class="st5" d="M215.9,195.1h-0.2c-0.5-0.1-1-0.2-1.5-0.3c-0.5-0.1-1-0.1-1.7-0.1c-1.3,0-2.5,0.3-3.8,0.9
|
||||
c-1.3,0.6-2.5,1.3-3.6,2.1l-3.7,16.1h-3.9l5.2-22.7h3.9l-0.8,3.3c1.8-1.3,3.3-2.1,4.6-2.6c1.3-0.5,2.5-0.7,3.6-0.7
|
||||
c0.7,0,1.2,0,1.4,0.1c0.3,0,0.7,0.1,1.3,0.2L215.9,195.1z" />
|
||||
<path class="st5" d="M225.2,214.2c-1.4,0-2.7-0.2-3.8-0.5c-1.1-0.3-2.1-0.9-2.9-1.6c-0.8-0.7-1.4-1.6-1.9-2.7
|
||||
c-0.4-1.1-0.7-2.3-0.7-3.7c0-2.1,0.3-4.1,1-5.9c0.7-1.8,1.6-3.5,2.8-4.9c1.2-1.4,2.6-2.4,4.4-3.2c1.7-0.8,3.6-1.2,5.6-1.2
|
||||
c1.3,0,2.6,0.2,3.8,0.5c1.2,0.4,2.2,0.8,3.1,1.3l-0.8,4.1h-0.2c-0.3-0.2-0.6-0.5-1-0.8c-0.4-0.3-0.9-0.6-1.4-0.9
|
||||
c-0.6-0.3-1.2-0.5-1.9-0.7c-0.7-0.2-1.5-0.3-2.3-0.3c-2.6,0-4.8,1.1-6.5,3.3c-1.7,2.2-2.5,4.9-2.5,8.1c0,1.9,0.5,3.4,1.5,4.4
|
||||
c1,1,2.4,1.5,4.2,1.5c0.9,0,1.7-0.1,2.6-0.4c0.9-0.2,1.6-0.5,2.2-0.8c0.7-0.3,1.3-0.6,1.9-1c0.6-0.3,1-0.6,1.2-0.8h0.2l-0.8,4.2
|
||||
c-1.2,0.5-2.4,1-3.8,1.4C227.9,214,226.5,214.2,225.2,214.2z" />
|
||||
<path class="st5" d="M259.9,196.2c0,0.3,0,0.8-0.1,1.3c-0.1,0.6-0.1,1-0.3,1.5l-3.4,14.7h-3.8l3-12.9c0.2-0.7,0.3-1.3,0.4-1.9
|
||||
c0.1-0.5,0.1-1.1,0.1-1.6c0-1.1-0.3-2-0.9-2.6c-0.6-0.6-1.6-0.9-3.1-0.9c-1,0-2.2,0.3-3.4,0.9c-1.2,0.6-2.5,1.2-3.7,2l-3.9,16.9
|
||||
h-3.8l7.3-31.6h3.8l-2.7,11.4c1.5-1,2.8-1.8,4.1-2.3c1.3-0.5,2.6-0.8,3.9-0.8c2,0,3.5,0.5,4.6,1.5
|
||||
C259.4,192.9,259.9,194.3,259.9,196.2z" />
|
||||
<path class="st5" d="M308.1,195.1c0,3.3-0.8,6.4-2.5,9.4c-1.7,3-4,5.2-6.9,6.8c-1.8,1-3.6,1.6-5.5,1.9c-1.9,0.3-4,0.5-6.4,0.5
|
||||
h-8.2l7-30.2h7.1c2.2,0,4.2,0.2,6,0.5c1.8,0.3,3.5,1,5,2c1.4,1,2.5,2.2,3.3,3.8C307.7,191.2,308.1,193,308.1,195.1z M303.8,195.4
|
||||
c0-1.5-0.3-2.9-0.8-4c-0.6-1.1-1.4-2-2.5-2.8c-1.1-0.7-2.3-1.2-3.5-1.5c-1.3-0.2-2.8-0.4-4.8-0.4h-3.4l-5.5,23.6h4.2
|
||||
c1.9,0,3.7-0.2,5.2-0.5c1.5-0.3,3-0.9,4.3-1.6c2.2-1.2,3.9-3,5-5.4C303.3,200.6,303.8,198.1,303.8,195.4z" />
|
||||
<path class="st5"
|
||||
d="M324.7,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
|
||||
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
|
||||
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
|
||||
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1h-0.2
|
||||
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
|
||||
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L324.7,211.3z M327,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
|
||||
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L327,201.8z" />
|
||||
<path class="st5" d="M352.5,191l-0.7,3.1h-7.9l-2.4,10.5c-0.1,0.5-0.3,1.1-0.4,1.8c-0.1,0.7-0.2,1.2-0.2,1.6c0,1,0.3,1.7,0.8,2.2
|
||||
c0.5,0.5,1.4,0.7,2.8,0.7c0.6,0,1.2-0.1,2-0.3c0.8-0.2,1.3-0.3,1.6-0.4h0.2l-0.7,3.3c-0.8,0.2-1.6,0.3-2.4,0.5
|
||||
c-0.9,0.1-1.6,0.2-2.3,0.2c-1.9,0-3.3-0.4-4.4-1.2c-1-0.8-1.5-2.1-1.5-3.9c0-0.4,0-0.9,0.1-1.3c0.1-0.4,0.1-0.9,0.3-1.5l2.8-12.2
|
||||
h-2.6l0.7-3.1h2.6l1.5-6.5h3.9l-1.5,6.5H352.5z" />
|
||||
<path class="st5"
|
||||
d="M366.3,211.3c-0.4,0.2-0.9,0.5-1.5,0.9c-0.6,0.4-1.3,0.7-1.9,1c-0.7,0.3-1.5,0.6-2.3,0.8
|
||||
c-0.9,0.2-1.8,0.3-3,0.3c-1.8,0-3.3-0.5-4.4-1.6c-1.1-1-1.7-2.4-1.7-4.1c0-1.8,0.4-3.3,1.2-4.6c0.8-1.2,2-2.2,3.5-3
|
||||
c1.5-0.8,3.4-1.3,5.6-1.7c2.2-0.3,4.6-0.6,7.4-0.6c0.1-0.4,0.2-0.7,0.2-1c0.1-0.3,0.1-0.6,0.1-0.9c0-0.6-0.1-1.2-0.4-1.6
|
||||
c-0.3-0.4-0.6-0.7-1.1-1c-0.5-0.2-1-0.4-1.7-0.5c-0.6-0.1-1.3-0.1-2.1-0.1c-1.2,0-2.5,0.2-4,0.6c-1.5,0.4-2.7,0.7-3.6,1.1h-0.2
|
||||
l0.8-3.8c0.8-0.2,1.9-0.4,3.4-0.7c1.5-0.3,2.9-0.4,4.3-0.4c2.8,0,5,0.4,6.4,1.3c1.4,0.9,2.1,2.3,2.1,4.2c0,0.4,0,0.8-0.1,1.2
|
||||
s-0.1,0.8-0.2,1.2l-3.6,15.4h-3.8L366.3,211.3z M368.5,201.8c-2.1,0.1-4,0.2-5.6,0.5c-1.6,0.2-3,0.6-4,1c-1.1,0.4-1.9,1.1-2.5,1.8
|
||||
c-0.6,0.8-0.9,1.7-0.9,2.9c0,1,0.3,1.7,1,2.2c0.7,0.5,1.7,0.8,3.1,0.8c1.2,0,2.5-0.3,3.8-0.8c1.3-0.5,2.5-1.2,3.6-2L368.5,201.8z" />
|
||||
<path class="st5" d="M417.4,213.7h-4.7l-8-12.1h-5.8l-2.8,12.1H392l7-30.2h8.3c1.9,0,3.4,0.1,4.5,0.4c1.2,0.2,2.2,0.7,3,1.3
|
||||
c0.8,0.6,1.4,1.2,1.8,2c0.4,0.8,0.7,1.8,0.7,2.9c0,2.5-0.8,4.7-2.4,6.6c-1.6,1.9-3.7,3.2-6.2,4L417.4,213.7z M412.8,191
|
||||
c0-0.7-0.1-1.3-0.3-1.8c-0.2-0.5-0.6-0.9-1-1.3c-0.5-0.4-1.2-0.7-1.9-0.9c-0.7-0.2-1.7-0.2-2.8-0.2h-4.6l-2.7,11.6h4.3
|
||||
c1.3,0,2.5-0.1,3.5-0.4c1-0.2,1.9-0.7,2.7-1.3c0.9-0.7,1.6-1.5,2.1-2.5C412.6,193.2,412.8,192.1,412.8,191z" />
|
||||
<path class="st5" d="M431.5,214.2c-3.2,0-5.6-0.7-7.4-2.2c-1.8-1.5-2.7-3.6-2.7-6.5c0-4.2,1.3-7.7,4-10.7s5.9-4.5,9.8-4.5
|
||||
c2.6,0,4.6,0.6,5.9,1.9c1.4,1.3,2.1,3.1,2.1,5.4c0,0.4-0.1,1-0.2,1.9c-0.1,0.9-0.3,1.9-0.6,3.1h-16.8c-0.1,0.4-0.1,0.8-0.2,1.2
|
||||
c0,0.4-0.1,0.7-0.1,1.1c0,1.9,0.6,3.4,1.8,4.5c1.2,1.1,2.8,1.6,5,1.6c1.5,0,3-0.3,4.6-0.9c1.6-0.6,2.9-1.2,4-2h0.2l-0.8,4.1
|
||||
c-0.7,0.2-1.3,0.5-1.8,0.7s-1.2,0.4-2.1,0.6c-0.8,0.2-1.6,0.4-2.2,0.5C433.3,214.2,432.5,214.2,431.5,214.2z M439.2,199.9
|
||||
c0.1-0.4,0.1-0.7,0.1-1c0-0.3,0-0.6,0-0.9c0-1.4-0.4-2.6-1.2-3.4c-0.8-0.8-2.1-1.2-3.8-1.2c-1.9,0-3.6,0.6-5.1,1.8
|
||||
c-1.5,1.2-2.5,2.8-3,4.7H439.2z" />
|
||||
<path class="st5" d="M468.8,198.2c0,2.2-0.4,4.4-1.1,6.3c-0.7,2-1.6,3.7-2.8,5c-1.2,1.4-2.5,2.5-4.1,3.4c-1.6,0.8-3.2,1.2-5,1.2
|
||||
c-1.2,0-2.4-0.1-3.4-0.4s-2-0.7-2.8-1.2l-2.2,9.5h-3.8l7.2-31h3.8l-0.6,2.4c1.3-0.9,2.5-1.6,3.7-2.2c1.2-0.6,2.6-0.8,4.1-0.8
|
||||
c2.2,0,3.9,0.7,5.1,2.1C468.2,193.8,468.8,195.7,468.8,198.2z M464.8,198.9c0-1.6-0.4-2.9-1.1-3.7c-0.7-0.9-1.8-1.3-3.4-1.3
|
||||
c-1.1,0-2.3,0.3-3.5,0.8c-1.2,0.6-2.3,1.2-3.4,1.9l-3,12.9c0.9,0.5,1.7,0.8,2.5,1.1c0.8,0.2,1.8,0.3,2.9,0.3c1.4,0,2.7-0.3,3.8-1
|
||||
s2.1-1.6,2.8-2.6c0.8-1.1,1.4-2.4,1.7-3.8C464.6,202,464.8,200.5,464.8,198.9z" />
|
||||
<path class="st5" d="M494.1,199.1c0,2-0.3,4-0.9,5.8c-0.6,1.9-1.5,3.5-2.7,4.9c-1.2,1.4-2.6,2.6-4.1,3.4c-1.6,0.8-3.4,1.2-5.4,1.2
|
||||
c-2.7,0-4.8-0.8-6.3-2.3c-1.5-1.5-2.3-3.7-2.3-6.4c0-2,0.3-4,0.9-5.8c0.6-1.8,1.5-3.5,2.7-4.9c1.1-1.4,2.5-2.5,4.2-3.3
|
||||
c1.6-0.8,3.4-1.2,5.4-1.2c2.6,0,4.7,0.7,6.3,2.2S494.1,196.3,494.1,199.1z M487.8,207.6c0.7-1.1,1.3-2.3,1.7-3.8s0.6-2.9,0.6-4.5
|
||||
c0-1.9-0.5-3.3-1.4-4.3c-0.9-1-2.2-1.5-3.9-1.5c-1.3,0-2.5,0.3-3.6,0.9c-1,0.6-2,1.5-2.7,2.6c-0.7,1.1-1.3,2.3-1.7,3.8
|
||||
c-0.4,1.4-0.6,2.9-0.6,4.5c0,1.9,0.5,3.3,1.4,4.3c0.9,1,2.2,1.5,3.9,1.5c1.3,0,2.5-0.3,3.6-0.9
|
||||
C486.2,209.6,487.1,208.7,487.8,207.6z" />
|
||||
<path class="st5" d="M504.2,214.3c-1.7,0-3.2-0.2-4.6-0.6c-1.3-0.4-2.5-0.9-3.4-1.4l0.9-4.1h0.2c0.3,0.2,0.7,0.5,1.1,0.9
|
||||
c0.5,0.3,1,0.7,1.7,1c0.6,0.3,1.4,0.6,2.2,0.8c0.8,0.2,1.7,0.3,2.6,0.3c1.9,0,3.4-0.4,4.5-1.1s1.6-1.7,1.6-3.1
|
||||
c0-0.7-0.3-1.3-0.8-1.7c-0.6-0.4-1.4-0.7-2.4-0.9c-0.5-0.1-1.2-0.3-1.9-0.4c-0.7-0.1-1.5-0.3-2.3-0.5c-1.6-0.4-2.8-1.1-3.5-1.9
|
||||
c-0.8-0.8-1.1-1.9-1.1-3.1c0-1.1,0.2-2,0.7-3c0.5-0.9,1.1-1.8,2.1-2.5c0.9-0.7,2-1.3,3.3-1.8c1.3-0.5,2.8-0.7,4.5-0.7
|
||||
c1.4,0,2.7,0.2,4.1,0.5c1.4,0.3,2.5,0.8,3.3,1.3l-0.8,3.9H516c-0.2-0.2-0.5-0.4-1-0.7c-0.4-0.3-1-0.6-1.7-0.9
|
||||
c-0.6-0.3-1.3-0.5-2.1-0.7c-0.8-0.2-1.6-0.3-2.4-0.3c-1.7,0-3.1,0.4-4.1,1.1c-1.1,0.7-1.6,1.7-1.6,2.9c0,0.7,0.3,1.2,0.8,1.7
|
||||
c0.5,0.5,1.3,0.8,2.4,1.1c0.7,0.2,1.4,0.3,2.1,0.5c0.7,0.1,1.4,0.3,2.1,0.5c1.6,0.4,2.8,1,3.6,1.8s1.2,1.9,1.2,3.1
|
||||
c0,1-0.2,2.1-0.7,3.1c-0.5,1-1.2,1.9-2.2,2.6c-1,0.8-2.1,1.4-3.5,1.8C507.4,214,505.9,214.3,504.2,214.3z" />
|
||||
<path class="st5" d="M528,191l-5.3,22.7h-3.8l5.3-22.7H528z M530.1,183.3l-0.9,4h-4.3l0.9-4H530.1z" />
|
||||
<path class="st5" d="M547.4,191l-0.7,3.1h-7.9l-2.4,10.5c-0.1,0.5-0.3,1.1-0.4,1.8c-0.1,0.7-0.2,1.2-0.2,1.6c0,1,0.3,1.7,0.8,2.2
|
||||
c0.5,0.5,1.4,0.7,2.8,0.7c0.6,0,1.2-0.1,2-0.3c0.8-0.2,1.3-0.3,1.6-0.4h0.2l-0.7,3.3c-0.8,0.2-1.6,0.3-2.4,0.5
|
||||
c-0.9,0.1-1.6,0.2-2.3,0.2c-1.9,0-3.3-0.4-4.4-1.2c-1-0.8-1.5-2.1-1.5-3.9c0-0.4,0-0.9,0.1-1.3c0.1-0.4,0.1-0.9,0.3-1.5l2.8-12.2
|
||||
h-2.6l0.7-3.1h2.6l1.5-6.5h3.9l-1.5,6.5H547.4z" />
|
||||
<path class="st5" d="M569.2,199.1c0,2-0.3,4-0.9,5.8c-0.6,1.9-1.5,3.5-2.7,4.9c-1.2,1.4-2.6,2.6-4.1,3.4c-1.5,0.8-3.4,1.2-5.4,1.2
|
||||
c-2.7,0-4.8-0.8-6.3-2.3c-1.5-1.5-2.3-3.7-2.3-6.4c0-2,0.3-4,0.9-5.8c0.6-1.8,1.5-3.5,2.7-4.9c1.1-1.4,2.5-2.5,4.2-3.3
|
||||
c1.6-0.8,3.4-1.2,5.4-1.2c2.6,0,4.7,0.7,6.3,2.2C568.4,194.1,569.2,196.3,569.2,199.1z M563,207.6c0.7-1.1,1.3-2.3,1.7-3.8
|
||||
c0.4-1.4,0.6-2.9,0.6-4.5c0-1.9-0.5-3.3-1.4-4.3c-0.9-1-2.2-1.5-3.9-1.5c-1.3,0-2.5,0.3-3.6,0.9c-1,0.6-2,1.5-2.7,2.6
|
||||
c-0.7,1.1-1.3,2.3-1.7,3.8c-0.4,1.4-0.6,2.9-0.6,4.5c0,1.9,0.5,3.3,1.4,4.3c0.9,1,2.2,1.5,3.9,1.5c1.3,0,2.5-0.3,3.6-0.9
|
||||
C561.3,209.6,562.2,208.7,563,207.6z" />
|
||||
<path class="st5" d="M590.6,195.1h-0.2c-0.5-0.1-1.1-0.2-1.5-0.3c-0.5-0.1-1-0.1-1.7-0.1c-1.3,0-2.5,0.3-3.8,0.9
|
||||
c-1.3,0.6-2.5,1.3-3.6,2.1l-3.7,16.1h-3.9l5.2-22.7h3.9l-0.8,3.3c1.8-1.3,3.3-2.1,4.6-2.6c1.3-0.5,2.5-0.7,3.6-0.7
|
||||
c0.7,0,1.2,0,1.4,0.1c0.3,0,0.7,0.1,1.3,0.2L590.6,195.1z" />
|
||||
<path class="st5" d="M594.1,222.1h-4.2l6.6-9.7l-4.1-21.3h4l3.2,16.9l10.9-16.9h4.2L594.1,222.1z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
171
resources/js/Components/NavBar.vue
Normal file
171
resources/js/Components/NavBar.vue
Normal file
|
@ -0,0 +1,171 @@
|
|||
<script lang="ts" setup>
|
||||
import { usePage, router } from '@inertiajs/vue3'
|
||||
// import { usePage } from '@inertiajs/inertia-vue3';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
import {ComputedRef} from "vue";
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { containerMaxW } from '@/config.js';
|
||||
// import { MainService } from '@/Stores/main.js';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import {
|
||||
mdiForwardburger,
|
||||
mdiBackburger,
|
||||
mdiClose,
|
||||
mdiDotsVertical,
|
||||
mdiMenu,
|
||||
// mdiClockOutline,
|
||||
mdiCloudDownloadOutline,
|
||||
mdiCloud,
|
||||
mdiCrop,
|
||||
mdiAccount,
|
||||
mdiCogOutline,
|
||||
mdiEmail,
|
||||
mdiLogout,
|
||||
mdiGithub,
|
||||
mdiThemeLightDark,
|
||||
} from '@mdi/js';
|
||||
import NavBarItem from '@/Components/NavBarItem.vue';
|
||||
import NavBarItemLabel from '@/Components/NavBarItemLabel.vue';
|
||||
import NavBarMenu from '@/Components/NavBarMenu.vue';
|
||||
import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
import UserAvatarCurrentUser from '@/Components/UserAvatarCurrentUser.vue';
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
import NavBarSearch from '@/Components/NavBarSearch.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import IUser from "App/Models/User";
|
||||
|
||||
// const mainStore = MainService();
|
||||
// const userName = computed(() =>mainStore.userName);
|
||||
|
||||
|
||||
|
||||
const styleService = StyleService();
|
||||
// const props = defineProps({
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// }
|
||||
// });
|
||||
// const userName = computed(() => usePage().props.user.login)
|
||||
|
||||
const user: ComputedRef<IUser>= computed(() => {
|
||||
return usePage().props.authUser as IUser;
|
||||
});
|
||||
// const userName = computed(() => props.user.login)
|
||||
|
||||
const toggleLightDark = () => {
|
||||
styleService.setDarkMode();
|
||||
};
|
||||
|
||||
const layoutStore = LayoutService();
|
||||
|
||||
const isMenuNavBarActive = ref(false);
|
||||
|
||||
const menuNavBarToggle = () => {
|
||||
isMenuNavBarActive.value = !isMenuNavBarActive.value;
|
||||
};
|
||||
|
||||
const menuOpenLg = () => {
|
||||
layoutStore.isAsideLgActive = true;
|
||||
};
|
||||
|
||||
// const logout = () => {
|
||||
// // router.post(route('logout'))
|
||||
// Inertia.get(stardust.route('app.index'));
|
||||
// };
|
||||
const logout = async() => {
|
||||
// router.post(route('logout'));
|
||||
await router.post(stardust.route('logout'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav
|
||||
class="top-0 left-0 right-0 fixed bg-gray-50 h-14 z-30 w-screen transition-position xl:pl-60 lg:w-auto dark:bg-slate-800"
|
||||
>
|
||||
<div class="flex lg:items-stretch" :class="containerMaxW">
|
||||
<div class="flex-1 items-stretch flex h-14">
|
||||
<NavBarItem type="flex lg:hidden" @click.prevent="layoutStore.asideMobileToggle()">
|
||||
<BaseIcon
|
||||
:path="layoutStore.isAsideMobileExpanded ? mdiBackburger : mdiForwardburger"
|
||||
size="24"
|
||||
/>
|
||||
</NavBarItem>
|
||||
<NavBarItem type="hidden lg:flex xl:hidden" @click.prevent="menuOpenLg">
|
||||
<BaseIcon :path="mdiMenu" size="24" />
|
||||
</NavBarItem>
|
||||
<NavBarItem>
|
||||
<NavBarSearch />
|
||||
</NavBarItem>
|
||||
</div>
|
||||
<div class="flex-none items-stretch flex h-14 lg:hidden">
|
||||
<NavBarItem @click.prevent="menuNavBarToggle">
|
||||
<BaseIcon :path="isMenuNavBarActive ? mdiClose : mdiDotsVertical" size="24" />
|
||||
</NavBarItem>
|
||||
</div>
|
||||
<div
|
||||
class="absolute w-screen top-14 left-0 bg-gray-50 shadow lg:w-auto lg:items-stretch lg:flex lg:grow lg:static lg:border-b-0 lg:overflow-visible lg:shadow-none dark:bg-slate-800"
|
||||
:class="[isMenuNavBarActive ? 'block' : 'hidden']"
|
||||
>
|
||||
<div
|
||||
class="max-h-screen-menu overflow-y-auto lg:overflow-visible lg:flex lg:items-stretch lg:justify-end lg:ml-auto"
|
||||
>
|
||||
<NavBarMenu>
|
||||
<NavBarItemLabel :icon="mdiMenu" label="Help menu" />
|
||||
<template #dropdown>
|
||||
<!-- <NavBarItem>
|
||||
<NavBarItemLabel :icon="mdiClockOutline" label="Item One" />
|
||||
</NavBarItem> -->
|
||||
<NavBarItem href="/docs/HandbuchTethys.pdf" target="_blank">
|
||||
<NavBarItemLabel :icon="mdiCloudDownloadOutline" label="Tethys Manual" />
|
||||
</NavBarItem>
|
||||
<NavBarItem href="/docs/geopackage_v01.pdf" target="_blank">
|
||||
<NavBarItemLabel :icon="mdiCloud" label="GeoPackage Help" />
|
||||
</NavBarItem>
|
||||
<BaseDivider nav-bar />
|
||||
<NavBarItem>
|
||||
<NavBarItemLabel :icon="mdiCrop" label="Item Last" />
|
||||
</NavBarItem>
|
||||
</template>
|
||||
</NavBarMenu>
|
||||
|
||||
<NavBarMenu>
|
||||
<NavBarItemLabel v-bind:label="user.login">
|
||||
<UserAvatarCurrentUser class="w-6 h-6 mr-3 inline-flex" />
|
||||
</NavBarItemLabel>
|
||||
|
||||
<template #dropdown>
|
||||
<!-- <NavBarItem> -->
|
||||
<!-- <NavBarItem route-name="admin.account.info"> -->
|
||||
<NavBarItem :route-name="'admin.account.info'" >
|
||||
<NavBarItemLabel :icon="mdiAccount" label="My Profile" />
|
||||
</NavBarItem>
|
||||
<NavBarItem :route-name="'settings'">
|
||||
<NavBarItemLabel :icon="mdiCogOutline" label="Settings" />
|
||||
</NavBarItem>
|
||||
<NavBarItem>
|
||||
<NavBarItemLabel :icon="mdiEmail" label="Messages" />
|
||||
</NavBarItem>
|
||||
<BaseDivider nav-bar />
|
||||
<NavBarItem @click="logout">
|
||||
<NavBarItemLabel :icon="mdiLogout" label="Log Out" />
|
||||
</NavBarItem>
|
||||
</template>
|
||||
</NavBarMenu>
|
||||
|
||||
<NavBarItem is-desktop-icon-only @click.prevent="toggleLightDark">
|
||||
<NavBarItemLabel v-bind:icon="mdiThemeLightDark" label="Light/Dark" is-desktop-icon-only />
|
||||
</NavBarItem>
|
||||
<NavBarItem href="https://gitea.geologie.ac.at/geolba/tethys" target="_blank" is-desktop-icon-only>
|
||||
<NavBarItemLabel v-bind:icon="mdiGithub" label="GitHub" is-desktop-icon-only />
|
||||
</NavBarItem>
|
||||
<NavBarItem is-desktop-icon-only @click="logout">
|
||||
<NavBarItemLabel v-bind:icon="mdiLogout" label="Log out" is-desktop-icon-only />
|
||||
</NavBarItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
90
resources/js/Components/NavBarItem.vue
Normal file
90
resources/js/Components/NavBarItem.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js'
|
||||
// import { Link } from '@inertiajs/vue3'
|
||||
import { Link } from '@inertiajs/vue3'
|
||||
import { computed } from 'vue'
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
routeName: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
param:{
|
||||
type:Number
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'flex'
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
isDesktopIconOnly: Boolean,
|
||||
dropdown: Boolean,
|
||||
active: Boolean
|
||||
})
|
||||
|
||||
const is = computed(() => {
|
||||
if (props.href) {
|
||||
return 'a'
|
||||
}
|
||||
|
||||
if (props.routeName) {
|
||||
return Link
|
||||
}
|
||||
|
||||
return 'div'
|
||||
})
|
||||
|
||||
const styleStore = StyleService()
|
||||
|
||||
const activeColor = props.activeColor ?? `${styleStore.navBarItemLabelActiveColorStyle} dark:text-slate-400`
|
||||
|
||||
const activeClass = computed(
|
||||
// () => props.routeName && route().current(props.routeName) == true ? props.activeColor : null
|
||||
() => props.routeName && stardust.isCurrent(props.routeName) ? props.activeColor : null
|
||||
)
|
||||
// const itemRoute = computed(() => (props.routeName ? stardust.route(props.routeName): ''));
|
||||
|
||||
const componentClass = computed(() => {
|
||||
const base = [
|
||||
props.type,
|
||||
props.active
|
||||
? activeColor
|
||||
: `${styleStore.navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${styleStore.navBarItemLabelHoverStyle}`
|
||||
]
|
||||
|
||||
if (props.type === 'block') {
|
||||
base.push('lg:flex')
|
||||
}
|
||||
|
||||
if (!props.dropdown) {
|
||||
base.push('py-2', 'px-3')
|
||||
} else {
|
||||
base.push('p-0', 'lg:py-2', 'lg:px-3')
|
||||
}
|
||||
|
||||
if (props.isDesktopIconOnly) {
|
||||
base.push('lg:w-16', 'lg:justify-center')
|
||||
}
|
||||
|
||||
return base
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="is"
|
||||
class="items-center grow-0 shrink-0 relative cursor-pointer"
|
||||
:class="[componentClass, activeClass]"
|
||||
:href="routeName ? stardust.route(props.routeName, [props.param]) : href"
|
||||
>
|
||||
<slot />
|
||||
</component>
|
||||
</template>
|
28
resources/js/Components/NavBarItemLabel.vue
Normal file
28
resources/js/Components/NavBarItemLabel.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script setup>
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
isDesktopIconOnly: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot />
|
||||
<BaseIcon
|
||||
v-if="icon"
|
||||
:path="icon"
|
||||
class="transition-colors"
|
||||
/>
|
||||
<span
|
||||
class="px-2 transition-colors"
|
||||
:class="{ 'lg:hidden':isDesktopIconOnly && icon }"
|
||||
>{{ label }}</span>
|
||||
</template>
|
62
resources/js/Components/NavBarMenu.vue
Normal file
62
resources/js/Components/NavBarMenu.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js'
|
||||
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
|
||||
import NavBarItem from '@/Components/NavBarItem.vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
|
||||
const styleStore = StyleService()
|
||||
|
||||
const isDropdownActive = ref(false)
|
||||
|
||||
const toggleDropdownIcon = computed(() => isDropdownActive.value ? mdiChevronUp : mdiChevronDown)
|
||||
|
||||
const toggle = () => {
|
||||
isDropdownActive.value = !isDropdownActive.value
|
||||
}
|
||||
|
||||
const root = ref(null)
|
||||
|
||||
const forceClose = event => {
|
||||
if (!root.value.$el.contains(event.target)) {
|
||||
isDropdownActive.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', forceClose)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', forceClose)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NavBarItem
|
||||
ref="root"
|
||||
type="block"
|
||||
:active="isDropdownActive"
|
||||
dropdown
|
||||
@click="toggle"
|
||||
>
|
||||
<a
|
||||
class="flex items-center py-2 px-3 dark:bg-slate-800 lg:bg-transparent lg:dark:bg-transparent"
|
||||
:class="styleStore.navBarMenuListUpperLabelStyle"
|
||||
>
|
||||
<slot />
|
||||
<BaseIcon
|
||||
:path="toggleDropdownIcon"
|
||||
class="hidden lg:inline-flex transition-colors"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
class="-mx-px text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute
|
||||
lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-b lg:dark:bg-slate-800
|
||||
dark:border-slate-700"
|
||||
:class="{'lg:hidden':!isDropdownActive}"
|
||||
>
|
||||
<slot name="dropdown" />
|
||||
</div>
|
||||
</NavBarItem>
|
||||
</template>
|
13
resources/js/Components/NavBarSearch.vue
Normal file
13
resources/js/Components/NavBarSearch.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormControl
|
||||
ref="root"
|
||||
placeholder="Search (ctrl+k)"
|
||||
ctrl-k-focus
|
||||
transparent
|
||||
borderless
|
||||
/>
|
||||
</template>
|
68
resources/js/Components/NotificationBar.vue
Normal file
68
resources/js/Components/NotificationBar.vue
Normal file
|
@ -0,0 +1,68 @@
|
|||
<script setup>
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import { mdiClose } from '@mdi/js'
|
||||
import { colorsBgLight, colorsOutline } from '@/colors.js'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
outline: Boolean,
|
||||
color: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const componentClass = computed(() => props.outline
|
||||
? colorsOutline[props.color]
|
||||
: colorsBgLight[props.color]
|
||||
)
|
||||
|
||||
const isDismissed = ref(false)
|
||||
|
||||
const dismiss = () => {
|
||||
isDismissed.value = true
|
||||
}
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const hasRightSlot = computed(() => slots.right)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!isDismissed"
|
||||
:class="componentClass"
|
||||
class="px-3 py-6 md:py-3 mb-6 last:mb-0 border rounded transition-colors duration-150"
|
||||
>
|
||||
<BaseLevel>
|
||||
<div class="flex flex-col md:flex-row items-center">
|
||||
<BaseIcon
|
||||
v-if="icon"
|
||||
:path="icon"
|
||||
w="w-10 md:w-5"
|
||||
h="h-10 md:h-5"
|
||||
size="24"
|
||||
class="md:mr-2"
|
||||
/>
|
||||
<span class="text-center md:text-left"><slot /></span>
|
||||
</div>
|
||||
<slot
|
||||
v-if="hasRightSlot"
|
||||
name="right"
|
||||
/>
|
||||
<BaseButton
|
||||
v-else
|
||||
:icon="mdiClose"
|
||||
:outline="outline"
|
||||
small
|
||||
@click="dismiss"
|
||||
/>
|
||||
</BaseLevel>
|
||||
</div>
|
||||
</template>
|
24
resources/js/Components/NotificationBarInCard.vue
Normal file
24
resources/js/Components/NotificationBarInCard.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script setup>
|
||||
import { colorsBgLight } from '@/colors.js';
|
||||
|
||||
defineProps({
|
||||
color: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isPlacedWithHeader: {
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col mb-6 -mt-6 -mr-6 -ml-6 animate-fade-in">
|
||||
<div
|
||||
:class="[colorsBgLight[color], { 'rounded-t-xl': !isPlacedWithHeader }]"
|
||||
class="flex flex-col p-6 transition-colors"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
63
resources/js/Components/NumberDynamic.vue
Normal file
63
resources/js/Components/NumberDynamic.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import numeral from 'numeral'
|
||||
|
||||
const props = defineProps({
|
||||
prefix: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500
|
||||
}
|
||||
})
|
||||
|
||||
const newValue = ref(0)
|
||||
|
||||
const newValueFormatted = computed(
|
||||
() => newValue.value < 1000 ? newValue.value : numeral(newValue.value).format('0,0')
|
||||
)
|
||||
|
||||
const value = computed(() => props.value)
|
||||
|
||||
const grow = m => {
|
||||
const v = Math.ceil(newValue.value + m)
|
||||
|
||||
if (v > value.value) {
|
||||
newValue.value = value.value
|
||||
return false
|
||||
}
|
||||
|
||||
newValue.value = v
|
||||
|
||||
setTimeout(() => {
|
||||
grow(m)
|
||||
}, 25)
|
||||
}
|
||||
|
||||
const growInit = () => {
|
||||
newValue.value = 0
|
||||
grow(props.value / (props.duration / 25))
|
||||
}
|
||||
|
||||
watch(value, () => {
|
||||
growInit()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
growInit()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>{{ prefix }}{{ newValueFormatted }}{{ suffix }}</div>
|
||||
</template>
|
33
resources/js/Components/OverlayLayer.vue
Normal file
33
resources/js/Components/OverlayLayer.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
|
||||
defineProps({
|
||||
zIndex: {
|
||||
type: String,
|
||||
default: 'z-50'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['overlay-click'])
|
||||
|
||||
const overlayClick = event => {
|
||||
emit('overlay-click', event)
|
||||
}
|
||||
|
||||
const styleStore = StyleService()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center flex-col justify-center overflow-hidden fixed inset-0" :class="zIndex">
|
||||
<transition enter-active-class="transition duration-150 ease-in" enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100" leave-active-class="transition duration-150 ease-in" leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0">
|
||||
<div class="absolute inset-0 bg-gradient-to-tr opacity-90 dark:from-gray-700 dark:via-gray-900 dark:to-gray-700"
|
||||
:class="styleStore.overlayStyle" @click="overlayClick" />
|
||||
</transition>
|
||||
<transition enter-active-class="transition duration-100 ease-out" enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100" leave-active-class="animate-fade-out">
|
||||
<slot />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
56
resources/js/Components/PillTag.vue
Normal file
56
resources/js/Components/PillTag.vue
Normal file
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { colorsBgLight, colorsOutline } from '@/colors.js'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
small: Boolean,
|
||||
outline: Boolean,
|
||||
wrapped: Boolean
|
||||
})
|
||||
|
||||
const componentClass = computed(() => {
|
||||
const baseColor = props.outline ? colorsOutline[props.type] : colorsBgLight[props.type]
|
||||
|
||||
const base = [
|
||||
'border',
|
||||
props.small ? 'py-1 px-4 text-xs rounded-full' : 'py-2 px-6 rounded-full',
|
||||
baseColor
|
||||
]
|
||||
|
||||
if (!props.wrapped) {
|
||||
base.push(props.small ? 'mr-1.5' : 'mr-3', 'last:mr-0')
|
||||
}
|
||||
|
||||
return base
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex items-center capitalize"
|
||||
:class="componentClass"
|
||||
>
|
||||
<BaseIcon
|
||||
v-if="icon"
|
||||
:path="icon"
|
||||
h="h-4"
|
||||
w="w-4"
|
||||
:class="small ? 'mr-1' : 'mr-2'"
|
||||
:size="small ? 14 : 16"
|
||||
/>
|
||||
<span>{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
53
resources/js/Components/PillTagTrend.vue
Normal file
53
resources/js/Components/PillTagTrend.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { mdiChevronUp, mdiChevronDown, mdiAlertCircleOutline } from '@mdi/js'
|
||||
import PillTag from '@/Components/PillTag.vue'
|
||||
|
||||
const props = defineProps({
|
||||
trend: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
trendType: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
small: Boolean
|
||||
})
|
||||
|
||||
const trendStyle = computed(() => {
|
||||
if (props.trendType === 'up') {
|
||||
return {
|
||||
icon: mdiChevronUp,
|
||||
style: 'success'
|
||||
}
|
||||
}
|
||||
|
||||
if (props.trendType === 'down') {
|
||||
return {
|
||||
icon: mdiChevronDown,
|
||||
style: 'danger'
|
||||
}
|
||||
}
|
||||
|
||||
if (props.trendType === 'alert') {
|
||||
return {
|
||||
icon: mdiAlertCircleOutline,
|
||||
style: 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
style: 'info'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PillTag
|
||||
:text="trend"
|
||||
:type="trendStyle.style"
|
||||
:icon="trendStyle.icon"
|
||||
:small="small"
|
||||
/>
|
||||
</template>
|
17
resources/js/Components/ResponsiveNavLink.vue
Normal file
17
resources/js/Components/ResponsiveNavLink.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps(['href', 'active']);
|
||||
|
||||
const classes = computed(() => props.active
|
||||
? 'block pl-3 pr-4 py-2 border-l-4 border-indigo-400 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="href" :class="classes">
|
||||
<slot />
|
||||
</Link>
|
||||
</template>
|
30
resources/js/Components/SectionBanner.vue
Normal file
30
resources/js/Components/SectionBanner.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { gradientBgPurplePink, gradientBgPinkRed, gradientBgGreenBlue } from '@/colors';
|
||||
|
||||
const props = defineProps({
|
||||
bg: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => ['purplePink', 'pinkRed', 'greenBlue'].includes(value),
|
||||
},
|
||||
});
|
||||
|
||||
const colorClass = computed(() => {
|
||||
switch (props.bg) {
|
||||
case 'purplePink':
|
||||
return gradientBgPurplePink;
|
||||
case 'pinkRed':
|
||||
return gradientBgPinkRed;
|
||||
case 'greenBlue':
|
||||
return gradientBgGreenBlue;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="colorClass" class="mt-6 mb-6 rounded-2xl py-12 px-6 text-center">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
19
resources/js/Components/SectionBannerStarOnGitea.vue
Normal file
19
resources/js/Components/SectionBannerStarOnGitea.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
import { mdiGithub } from '@mdi/js';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import SectionBanner from '@/Components/SectionBanner.vue';
|
||||
</script>
|
||||
<template>
|
||||
<SectionBanner bg="greenBlue">
|
||||
<h1 class="text-3xl text-white mb-6">Like the project? Please star on <b>Gitea</b>!</h1>
|
||||
<div>
|
||||
<BaseButton
|
||||
href="https://gitea.geologie.ac.at/geolba/tethys"
|
||||
:icon="mdiGithub"
|
||||
label="Gitea"
|
||||
target="_blank"
|
||||
rounded-full
|
||||
/>
|
||||
</div>
|
||||
</SectionBanner>
|
||||
</template>
|
39
resources/js/Components/SectionFullScreen.vue
Normal file
39
resources/js/Components/SectionFullScreen.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { StyleService } from '@/Stores/style'
|
||||
import { gradientBgPurplePink, gradientBgDark, gradientBgPinkRed, gradientBgGreenBlue } from '@/colors'
|
||||
|
||||
const props = defineProps({
|
||||
bg: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['purplePink', 'pinkRed', 'greenBlue'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
const colorClass = computed(() => {
|
||||
if (StyleService().darkMode) {
|
||||
return gradientBgDark
|
||||
}
|
||||
|
||||
switch (props.bg) {
|
||||
case 'purplePink':
|
||||
return gradientBgPurplePink
|
||||
case 'pinkRed':
|
||||
return gradientBgPinkRed
|
||||
case 'greenBlue':
|
||||
return gradientBgGreenBlue
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex min-h-screen items-center justify-center"
|
||||
:class="colorClass"
|
||||
>
|
||||
<slot card-class="w-11/12 md:w-7/12 lg:w-6/12 xl:w-4/12 shadow-2xl" />
|
||||
</div>
|
||||
</template>
|
9
resources/js/Components/SectionMain.vue
Normal file
9
resources/js/Components/SectionMain.vue
Normal file
|
@ -0,0 +1,9 @@
|
|||
<script setup>
|
||||
import { containerMaxW } from '@/config.js'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="p-6" v-bind:class="containerMaxW">
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
22
resources/js/Components/SectionTitle.vue
Normal file
22
resources/js/Components/SectionTitle.vue
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
custom: Boolean,
|
||||
first: Boolean,
|
||||
last: Boolean
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="py-24 px-6 lg:px-0 lg:max-w-2xl lg:mx-auto text-center"
|
||||
:class="{ '-mb-6':first, '-mt-6':last, '-my-6':!first && !last }"
|
||||
>
|
||||
<slot v-if="custom" />
|
||||
<h1
|
||||
v-else
|
||||
class="text-2xl text-gray-500 dark:text-slate-400"
|
||||
>
|
||||
<slot />
|
||||
</h1>
|
||||
</section>
|
||||
</template>
|
56
resources/js/Components/SectionTitleLineWithButton.vue
Normal file
56
resources/js/Components/SectionTitleLineWithButton.vue
Normal file
|
@ -0,0 +1,56 @@
|
|||
<script setup>
|
||||
import { mdiCog } from '@mdi/js'
|
||||
import { useSlots, computed } from 'vue'
|
||||
import BaseIcon from '@/Components/BaseIcon.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import IconRounded from '@/Components/IconRounded.vue'
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
main: Boolean
|
||||
})
|
||||
|
||||
const hasSlot = computed(() => useSlots().default)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:class="{'pt-6':!main}"
|
||||
class="mb-6 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex items-center justify-start">
|
||||
<IconRounded
|
||||
v-if="icon && main"
|
||||
:icon="icon"
|
||||
type="light"
|
||||
class="mr-3"
|
||||
bg
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else-if="icon"
|
||||
:path="icon"
|
||||
class="mr-2"
|
||||
size="20"
|
||||
/>
|
||||
<h1
|
||||
:class="main ? 'text-3xl' : 'text-2xl'"
|
||||
class="leading-tight"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
</div>
|
||||
<slot v-if="hasSlot" />
|
||||
<BaseButton
|
||||
v-else
|
||||
:icon="mdiCog"
|
||||
small
|
||||
/>
|
||||
</section>
|
||||
</template>
|
33
resources/js/Components/TableCheckboxCell.vue
Normal file
33
resources/js/Components/TableCheckboxCell.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'td'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['checked'])
|
||||
|
||||
const checked = ref(false)
|
||||
|
||||
watch(checked, newVal => {
|
||||
emit('checked', newVal)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="type"
|
||||
class="lg:w-1"
|
||||
>
|
||||
<label class="checkbox">
|
||||
<input
|
||||
v-model="checked"
|
||||
type="checkbox"
|
||||
>
|
||||
<span class="check" />
|
||||
</label>
|
||||
</component>
|
||||
</template>
|
140
resources/js/Components/TableSampleClients.vue
Normal file
140
resources/js/Components/TableSampleClients.vue
Normal file
|
@ -0,0 +1,140 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { StyleService } from '@/Stores/style';
|
||||
import { mdiEye, mdiTrashCan } from '@mdi/js';
|
||||
import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
import TableCheckboxCell from '@/Components/TableCheckboxCell.vue';
|
||||
import BaseLevel from '@/Components/BaseLevel.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import UserAvatar from '@/Components/UserAvatar.vue';
|
||||
|
||||
defineProps({
|
||||
checkable: Boolean,
|
||||
});
|
||||
|
||||
const styleService = StyleService();
|
||||
const mainService = MainService();
|
||||
const items = computed(() => mainService.clients);
|
||||
|
||||
const isModalActive = ref(false);
|
||||
const isModalDangerActive = ref(false);
|
||||
const perPage = ref(5);
|
||||
const currentPage = ref(0);
|
||||
const checkedRows = ref([]);
|
||||
|
||||
const itemsPaginated = computed(() =>
|
||||
items.value.slice(perPage.value * currentPage.value, perPage.value * (currentPage.value + 1))
|
||||
);
|
||||
|
||||
const numPages = computed(() => Math.ceil(items.value.length / perPage.value));
|
||||
|
||||
const currentPageHuman = computed(() => currentPage.value + 1);
|
||||
|
||||
const pagesList = computed(() => {
|
||||
const pagesList = [];
|
||||
|
||||
for (let i = 0; i < numPages.value; i++) {
|
||||
pagesList.push(i);
|
||||
}
|
||||
|
||||
return pagesList;
|
||||
});
|
||||
|
||||
const remove = (arr, cb) => {
|
||||
const newArr = [];
|
||||
|
||||
arr.forEach((item) => {
|
||||
if (!cb(item)) {
|
||||
newArr.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return newArr;
|
||||
};
|
||||
|
||||
const checked = (isChecked, client) => {
|
||||
if (isChecked) {
|
||||
checkedRows.value.push(client);
|
||||
} else {
|
||||
checkedRows.value = remove(checkedRows.value, (row) => row.id === client.id);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBoxModal v-model="isModalActive" title="Sample modal">
|
||||
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||
<p>This is sample modal</p>
|
||||
</CardBoxModal>
|
||||
|
||||
<CardBoxModal v-model="isModalDangerActive" large-title="Please confirm" button="danger" has-cancel>
|
||||
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||
<p>This is sample modal</p>
|
||||
</CardBoxModal>
|
||||
|
||||
<div v-if="checkedRows.length" class="p-3 bg-gray-100/50 dark:bg-slate-800">
|
||||
<span v-for="checkedRow in checkedRows" :key="checkedRow.id"
|
||||
class="inline-block px-2 py-1 rounded-sm mr-2 text-sm bg-gray-100 dark:bg-slate-700">
|
||||
{{ checkedRow.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="checkable" />
|
||||
<th />
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>City</th>
|
||||
<th>Progress</th>
|
||||
<th>Created</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="client in itemsPaginated" :key="client.id">
|
||||
<TableCheckboxCell v-if="checkable" @checked="checked($event, client)" />
|
||||
<td class="border-b-0 lg:w-6 before:hidden">
|
||||
<UserAvatar :username="client.name" class="w-24 h-24 mx-auto lg:w-6 lg:h-6" />
|
||||
</td>
|
||||
<td data-label="Name">
|
||||
{{ client.name }}
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
{{ client.email }}
|
||||
</td>
|
||||
<td data-label="City">
|
||||
{{ client.city }}
|
||||
</td>
|
||||
<td data-label="Progress" class="lg:w-32">
|
||||
<progress class="flex w-2/5 self-center lg:w-full" max="100" v-bind:value="client.progress">
|
||||
{{ client.progress }}
|
||||
</progress>
|
||||
</td>
|
||||
<td data-label="Created" class="lg:w-1 whitespace-nowrap">
|
||||
<small class="text-gray-500 dark:text-slate-400" :title="client.created">{{
|
||||
client.created
|
||||
}}</small>
|
||||
</td>
|
||||
<td class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" />
|
||||
<BaseButton color="danger" :icon="mdiTrashCan" small @click="isModalDangerActive = true" />
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="p-3 lg:px-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<BaseLevel>
|
||||
<BaseButtons>
|
||||
<BaseButton v-for="page in pagesList" :key="page" :active="page === currentPage" :label="page + 1" small
|
||||
:outline="styleService.darkMode" @click="currentPage = page" />
|
||||
</BaseButtons>
|
||||
<small>Page {{ currentPageHuman }} of {{ numPages }}</small>
|
||||
</BaseLevel>
|
||||
</div>
|
||||
</template>
|
41
resources/js/Components/UserAvatar.vue
Normal file
41
resources/js/Components/UserAvatar.vue
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
api: {
|
||||
type: String,
|
||||
default: 'avataaars',
|
||||
},
|
||||
});
|
||||
|
||||
const avatar = computed(
|
||||
// () => props.avatar ?? `https://avatars.dicebear.com/api/${props.api}/${props.username?.replace(/[^a-z0-9]+/i, '-')}.svg`
|
||||
|
||||
() => props.avatar ?? `https://avatars.dicebear.com/api/initials/${props.username}.svg`
|
||||
|
||||
// () => {
|
||||
|
||||
// return props.avatar ?? `https://www.gravatar.com/avatar/${props.username}?s=50`;
|
||||
// }
|
||||
);
|
||||
|
||||
const username = computed(() => props.username);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img
|
||||
:src="avatar"
|
||||
:alt="username"
|
||||
class="rounded-full block h-auto w-full max-w-full bg-gray-100 dark:bg-slate-800"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
15
resources/js/Components/UserAvatarCurrentUser.vue
Normal file
15
resources/js/Components/UserAvatarCurrentUser.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
// import { usePage } from '@inertiajs/vue3'
|
||||
import { usePage } from '@inertiajs/vue3'
|
||||
import UserAvatar from '@/Components/UserAvatar.vue'
|
||||
|
||||
const userName = computed(() => usePage().props.auth?.user.name)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserAvatar
|
||||
v-bind:username="'userName'"
|
||||
api="initials"
|
||||
/>
|
||||
</template>
|
7
resources/js/Components/unused/ApplicationLogo.vue
Normal file
7
resources/js/Components/unused/ApplicationLogo.vue
Normal file
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
14
resources/js/Components/unused/Button.vue
Normal file
14
resources/js/Components/unused/Button.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: 'submit',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :type="type" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:shadow-outline-gray transition ease-in-out duration-150">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
30
resources/js/Components/unused/Checkbox.vue
Normal file
30
resources/js/Components/unused/Checkbox.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:checked']);
|
||||
|
||||
const props = defineProps({
|
||||
checked: {
|
||||
type: [Array, Boolean],
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const proxyChecked = computed({
|
||||
get() {
|
||||
return props.checked;
|
||||
},
|
||||
|
||||
set(val) {
|
||||
emit("update:checked", val);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input type="checkbox" :value="value" v-model="proxyChecked"
|
||||
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
|
||||
</template>
|
71
resources/js/Components/unused/Dropdown.vue
Normal file
71
resources/js/Components/unused/Dropdown.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
align: {
|
||||
default: 'right'
|
||||
},
|
||||
width: {
|
||||
default: '48'
|
||||
},
|
||||
contentClasses: {
|
||||
default: () => ['py-1', 'bg-white']
|
||||
}
|
||||
});
|
||||
|
||||
const closeOnEscape = (e) => {
|
||||
if (open.value && e.key === 'Escape') {
|
||||
open.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', closeOnEscape));
|
||||
onUnmounted(() => document.removeEventListener('keydown', closeOnEscape));
|
||||
|
||||
const widthClass = computed(() => {
|
||||
return {
|
||||
'48': 'w-48',
|
||||
}[props.width.toString()];
|
||||
});
|
||||
|
||||
const alignmentClasses = computed(() => {
|
||||
if (props.align === 'left') {
|
||||
return 'origin-top-left left-0';
|
||||
} else if (props.align === 'right') {
|
||||
return 'origin-top-right right-0';
|
||||
} else {
|
||||
return 'origin-top';
|
||||
}
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div @click="open = ! open">
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
|
||||
<!-- Full Screen Dropdown Overlay -->
|
||||
<div v-show="open" class="fixed inset-0 z-40" @click="open = false"></div>
|
||||
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="transform opacity-0 scale-95"
|
||||
enter-to-class="transform opacity-100 scale-100"
|
||||
leave-active-class="transition ease-in duration-75"
|
||||
leave-from-class="transform opacity-100 scale-100"
|
||||
leave-to-class="transform opacity-0 scale-95">
|
||||
<div v-show="open"
|
||||
class="absolute z-50 mt-2 rounded-md shadow-lg"
|
||||
:class="[widthClass, alignmentClasses]"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5" :class="contentClasses">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
9
resources/js/Components/unused/DropdownLink.vue
Normal file
9
resources/js/Components/unused/DropdownLink.vue
Normal file
|
@ -0,0 +1,9 @@
|
|||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out">
|
||||
<slot />
|
||||
</Link>
|
||||
</template>
|
19
resources/js/Components/unused/Input.vue
Normal file
19
resources/js/Components/unused/Input.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
defineProps(['modelValue']);
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
const input = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
if (input.value.hasAttribute('autofocus')) {
|
||||
input.value.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" ref="input">
|
||||
</template>
|
11
resources/js/Components/unused/InputError.vue
Normal file
11
resources/js/Components/unused/InputError.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<script setup>
|
||||
defineProps(['message']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="message">
|
||||
<p class="text-sm text-red-600">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
10
resources/js/Components/unused/Label.vue
Normal file
10
resources/js/Components/unused/Label.vue
Normal file
|
@ -0,0 +1,10 @@
|
|||
<script setup>
|
||||
defineProps(['value']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="block font-medium text-sm text-gray-700">
|
||||
<span v-if="value">{{ value }}</span>
|
||||
<span v-else><slot /></span>
|
||||
</label>
|
||||
</template>
|
17
resources/js/Components/unused/NavLink.vue
Normal file
17
resources/js/Components/unused/NavLink.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
|
||||
const props = defineProps(['href', 'active']);
|
||||
|
||||
const classes = computed(() => props.active
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Link :href="href" :class="classes">
|
||||
<slot />
|
||||
</Link>
|
||||
</template>
|
45
resources/js/Components/unused/UserCard.vue
Normal file
45
resources/js/Components/unused/UserCard.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { MainService } from '@/Stores/main'
|
||||
import { mdiCheckDecagram } from '@mdi/js'
|
||||
import BaseLevel from '@/Components/BaseLevel.vue'
|
||||
import UserAvatarCurrentUser from '@/Components/UserAvatarCurrentUser.vue'
|
||||
import CardBox from '@/Components/CardBox.vue'
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue'
|
||||
import PillTag from '@/Components/PillTag.vue'
|
||||
|
||||
const mainService = MainService()
|
||||
|
||||
const userName = computed(() => mainService.userName)
|
||||
|
||||
const userSwitchVal = ref([])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBox>
|
||||
<BaseLevel type="justify-around lg:justify-center">
|
||||
<UserAvatarCurrentUser class="lg:mx-12" />
|
||||
<div class="space-y-3 text-center md:text-left lg:mx-12">
|
||||
<div class="flex justify-center md:block">
|
||||
<FormCheckRadioGroup
|
||||
v-model="userSwitchVal"
|
||||
name="sample-switch"
|
||||
type="switch"
|
||||
:options="{ one: 'Notifications' }"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="text-2xl">
|
||||
Howdy, <b>{{ userName }}</b>!
|
||||
</h1>
|
||||
<p>Last login <b>12 mins ago</b> from <b>127.0.0.1</b></p>
|
||||
<div class="flex justify-center md:block">
|
||||
<PillTag
|
||||
text="Verified"
|
||||
type="info"
|
||||
:icon="mdiCheckDecagram"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</template>
|
18
resources/js/Components/unused/ValidationErrors.vue
Normal file
18
resources/js/Components/unused/ValidationErrors.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
|
||||
const errors = computed(() => usePage().props.errors);
|
||||
|
||||
const hasErrors = computed(() => Object.keys(errors.value).length > 0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasErrors">
|
||||
<div class="font-medium text-red-600">Whoops! Something went wrong.</div>
|
||||
|
||||
<ul class="mt-3 list-disc list-inside text-sm text-red-600">
|
||||
<li v-for="(error, key) in errors" :key="key">{{ error }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
14
resources/js/Layouts/Auth.vue
Normal file
14
resources/js/Layouts/Auth.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- <h1>Auth Layout</h1>
|
||||
<slot></slot> -->
|
||||
<Header />
|
||||
<!-- <main class="px-6"> -->
|
||||
<slot></slot>
|
||||
<!-- </main> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from '@/Components/Header.vue';
|
||||
</script>
|
14
resources/js/Layouts/Default.vue
Normal file
14
resources/js/Layouts/Default.vue
Normal file
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- <h1>Default Layout</h1>
|
||||
<slot></slot> -->
|
||||
<Header />
|
||||
<main class="px-6">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from '@/Components/Header.vue';
|
||||
</script>
|
33
resources/js/Layouts/LayoutAuthenticated.vue
Normal file
33
resources/js/Layouts/LayoutAuthenticated.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script lang="ts" setup>
|
||||
import { LayoutService } from '@/Stores/layout.js'
|
||||
import { StyleService } from '@/Stores/style'
|
||||
import NavBar from '@/Components/NavBar.vue'
|
||||
import AsideMenu from '@/Components/AsideMenu.vue'
|
||||
import FooterBar from '@/Components/FooterBar.vue'
|
||||
|
||||
const styleService = StyleService()
|
||||
|
||||
const layoutService = LayoutService()
|
||||
|
||||
// defineProps({
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// }
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'dark': styleService.darkMode, 'overflow-hidden lg:overflow-visible': layoutService.isAsideMobileExpanded }">
|
||||
<div
|
||||
:class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }"
|
||||
class="pt-14 xl:pl-60 min-h-screen w-screen transition-position lg:w-auto bg-gray-50 dark:bg-slate-800 dark:text-slate-100"
|
||||
>
|
||||
<NavBar :class="{ 'ml-60 lg:ml-0': layoutService.isAsideMobileExpanded }" />
|
||||
<AsideMenu />
|
||||
<slot></slot>
|
||||
<FooterBar />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
13
resources/js/Layouts/LayoutGuest.vue
Normal file
13
resources/js/Layouts/LayoutGuest.vue
Normal file
|
@ -0,0 +1,13 @@
|
|||
<script setup>
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
const styleService = StyleService();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- dark; true or false from pinia service -->
|
||||
<div :class="{ dark: styleService.darkMode }">
|
||||
<div class="bg-gray-50 dark:bg-slate-800 dark:text-slate-100">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
72
resources/js/Pages/Admin/Permission/Create.vue
Normal file
72
resources/js/Pages/Admin/Permission/Create.vue
Normal file
|
@ -0,0 +1,72 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import FormField from '@/Components/FormField.vue'
|
||||
import FormControl from '@/Components/FormControl.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Create permission" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
:icon="mdiAccountKey"
|
||||
title="Add permission"
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
:route-name="route('permission.index')"
|
||||
:icon="mdiArrowLeftBoldOutline"
|
||||
label="Back"
|
||||
color="white"
|
||||
rounded-full
|
||||
small
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox
|
||||
form
|
||||
@submit.prevent="form.post(route('permission.store'))"
|
||||
>
|
||||
<FormField
|
||||
label="Name"
|
||||
:class="{ 'text-red-400': form.errors.name }"
|
||||
>
|
||||
<FormControl
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="Enter Name"
|
||||
:error="form.errors.name"
|
||||
>
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.name">
|
||||
{{ form.errors.name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="info"
|
||||
label="Submit"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
/>
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
80
resources/js/Pages/Admin/Permission/Edit.vue
Normal file
80
resources/js/Pages/Admin/Permission/Edit.vue
Normal file
|
@ -0,0 +1,80 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import FormField from '@/Components/FormField.vue'
|
||||
import FormControl from '@/Components/FormControl.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
|
||||
const props = defineProps({
|
||||
permission: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
_method: 'put',
|
||||
name: props.permission.name,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Update permission" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
:icon="mdiAccountKey"
|
||||
title="Update permission"
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
:route-name="route('permission.index')"
|
||||
:icon="mdiArrowLeftBoldOutline"
|
||||
label="Back"
|
||||
color="white"
|
||||
rounded-full
|
||||
small
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox
|
||||
form
|
||||
@submit.prevent="form.post(route('permission.update', props.permission.id))"
|
||||
>
|
||||
<FormField
|
||||
label="Name"
|
||||
:class="{ 'text-red-400': form.errors.name }"
|
||||
>
|
||||
<FormControl
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="Enter Name"
|
||||
:error="form.errors.name"
|
||||
>
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.name">
|
||||
{{ form.errors.name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="info"
|
||||
label="Submit"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
/>
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
158
resources/js/Pages/Admin/Permission/Index.vue
Normal file
158
resources/js/Pages/Admin/Permission/Index.vue
Normal file
|
@ -0,0 +1,158 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiPlus,
|
||||
mdiSquareEditOutline,
|
||||
mdiTrashCan,
|
||||
mdiAlertBoxOutline,
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import BaseButton from "@/Components/BaseButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import BaseButtons from "@/Components/BaseButtons.vue"
|
||||
import NotificationBar from "@/Components/NotificationBar.vue"
|
||||
import Pagination from "@/Components/Admin/Pagination.vue"
|
||||
import Sort from "@/Components/Admin/Sort.vue"
|
||||
|
||||
const props = defineProps({
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
filters: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
can: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
search: props.filters.search,
|
||||
})
|
||||
|
||||
const formDelete = useForm({})
|
||||
|
||||
function destroy(id) {
|
||||
if (confirm("Are you sure you want to delete?")) {
|
||||
formDelete.delete(route("permission.destroy", id))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Permissions" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
:icon="mdiAccountKey"
|
||||
title="Permissions"
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
v-if="can.delete"
|
||||
:route-name="route('permission.create')"
|
||||
:icon="mdiPlus"
|
||||
label="Add"
|
||||
color="info"
|
||||
rounded-full
|
||||
small
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<NotificationBar
|
||||
v-if="$page.props.flash.message"
|
||||
color="success"
|
||||
:icon="mdiAlertBoxOutline"
|
||||
>
|
||||
{{ $page.props.flash.message }}
|
||||
</NotificationBar>
|
||||
<CardBox class="mb-6" has-table>
|
||||
<form @submit.prevent="form.get(route('permission.index'))">
|
||||
<div class="py-2 flex">
|
||||
<div class="flex pl-4">
|
||||
<input
|
||||
type="search"
|
||||
v-model="form.search"
|
||||
class="
|
||||
rounded-md
|
||||
shadow-sm
|
||||
border-gray-300
|
||||
focus:border-indigo-300
|
||||
focus:ring
|
||||
focus:ring-indigo-200
|
||||
focus:ring-opacity-50
|
||||
"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<BaseButton
|
||||
label="Search"
|
||||
type="submit"
|
||||
color="info"
|
||||
class="ml-4 inline-flex items-center px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardBox>
|
||||
<CardBox class="mb-6" has-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Sort label="Name" attribute="name" />
|
||||
</th>
|
||||
<th v-if="can.edit || can.delete">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="permission in permissions.data" :key="permission.id">
|
||||
<td data-label="Name">
|
||||
<Link
|
||||
:href="route('permission.show', permission.id)"
|
||||
class="
|
||||
no-underline
|
||||
hover:underline
|
||||
text-cyan-600
|
||||
dark:text-cyan-400
|
||||
"
|
||||
>
|
||||
{{ permission.name }}
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
v-if="can.edit || can.delete"
|
||||
class="before:hidden lg:w-1 whitespace-nowrap"
|
||||
>
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton
|
||||
v-if="can.edit"
|
||||
:route-name="route('permission.edit', permission.id)"
|
||||
color="info"
|
||||
:icon="mdiSquareEditOutline"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="can.delete"
|
||||
color="danger"
|
||||
:icon="mdiTrashCan"
|
||||
small
|
||||
@click="destroy(permission.id)"
|
||||
/>
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="py-4">
|
||||
<Pagination :data="permissions" />
|
||||
</div>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
81
resources/js/Pages/Admin/Permission/Show.vue
Normal file
81
resources/js/Pages/Admin/Permission/Show.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline,
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import BaseButton from "@/Components/BaseButton.vue"
|
||||
|
||||
const props = defineProps({
|
||||
permission: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="View permission" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
:icon="mdiAccountKey"
|
||||
title="View permission"
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
:route-name="route('permission.index')"
|
||||
:icon="mdiArrowLeftBoldOutline"
|
||||
label="Back"
|
||||
color="white"
|
||||
rounded-full
|
||||
small
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox class="mb-6">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
"
|
||||
>
|
||||
Name
|
||||
</td>
|
||||
<td data-label="Name">
|
||||
{{ permission.name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
"
|
||||
>
|
||||
Created
|
||||
</td>
|
||||
<td data-label="Created">
|
||||
{{ new Date(permission.created_at).toLocaleString() }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
94
resources/js/Pages/Admin/Role/Create.vue
Normal file
94
resources/js/Pages/Admin/Role/Create.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm, router } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline,
|
||||
mdiAccount, mdiNoteText, mdiFormTextarea
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import FormField from '@/Components/FormField.vue'
|
||||
import FormControl from '@/Components/FormControl.vue'
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue'
|
||||
import BaseDivider from '@/Components/BaseDivider.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
name: '',
|
||||
display_name: '',
|
||||
description: '',
|
||||
permissions: []
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
await form.post(stardust.route('role.store'), form);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Add role" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Add role" main>
|
||||
<BaseButton :route-name="stardust.route('role.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
|
||||
rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- <CardBox form @submit.prevent="form.post(stardust.route('role.store'))"> -->
|
||||
<CardBox form @submit.prevent="submit()">
|
||||
|
||||
<FormField label="Name" help="Required. Role name" :class="{ 'text-red-400': form.errors.name }">
|
||||
<FormControl v-model="form.name" type="text" placeholder="Enter Name" required :error="form.errors.name">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.name">
|
||||
{{ form.errors.name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Display Name" help="Optional. Display name" :class="{ 'text-red-400': form.errors.display_name }">
|
||||
<FormControl v-model="form.display_name" name="display_name" :error="form.errors.display_name">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.display_name">
|
||||
{{ form.errors.display_name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Description" help="Optional. Description of new role" :class="{ 'text-red-400': form.errors.description }">
|
||||
<FormControl v-model="form.description" v-bind:icon="mdiFormTextarea" name="display_name" :type="'textarea'" :error="form.errors.description">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.description">
|
||||
{{ form.errors.description }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="Permissions" wrap-body>
|
||||
<FormCheckRadioGroup v-model="form.permissions" name="permissions" is-column :options="props.permissions" />
|
||||
</FormField>
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.permissions && Array.isArray(form.errors.permissions)">
|
||||
<!-- {{ errors.password_confirmation }} -->
|
||||
{{ form.errors.permissions.join(', ') }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
91
resources/js/Pages/Admin/Role/Edit.vue
Normal file
91
resources/js/Pages/Admin/Role/Edit.vue
Normal file
|
@ -0,0 +1,91 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline,
|
||||
mdiFormTextarea
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import FormField from '@/Components/FormField.vue'
|
||||
import FormControl from '@/Components/FormControl.vue'
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue'
|
||||
import BaseDivider from '@/Components/BaseDivider.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
role: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
roleHasPermissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
_method: 'put',
|
||||
name: props.role.name,
|
||||
description: props.role.description,
|
||||
permissions: props.roleHasPermissions
|
||||
})
|
||||
|
||||
const submit = async () => {
|
||||
// await Inertia.post(stardust.route('user.store'), form);
|
||||
await form.put(stardust.route('role.update', [props.role.id]), form);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Update role" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Update role" main>
|
||||
<BaseButton :route-name="stardust.route('role.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
|
||||
rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- <CardBox form @submit.prevent="form.put(stardust.route('role.update', [props.role.id]))"> -->
|
||||
<CardBox form @submit.prevent="submit()">
|
||||
|
||||
<FormField label="Name" :class="{ 'text-red-400': form.errors.name }">
|
||||
<FormControl v-model="form.name" type="text" placeholder="Enter Name" required :error="form.errors.name">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.name">
|
||||
{{ form.errors.name }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Description" help="Optional. Description of new role" :class="{ 'text-red-400': form.errors.description }">
|
||||
<FormControl v-model="form.description" v-bind:icon="mdiFormTextarea" name="display_name" :type="'textarea'" :error="form.errors.description">
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.description">
|
||||
{{ form.errors.description }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="Permissions" wrap-body>
|
||||
<FormCheckRadioGroup v-model="form.permissions" name="permissions" is-column :options="props.permissions" />
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
173
resources/js/Pages/Admin/Role/Index.vue
Normal file
173
resources/js/Pages/Admin/Role/Index.vue
Normal file
|
@ -0,0 +1,173 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiPlus, mdiSquareEditOutline, mdiTrashCan, mdiAlertBoxOutline } from '@mdi/js';
|
||||
import { computed, ref } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import Pagination from '@/Components/Admin/Pagination.vue';
|
||||
import Sort from '@/Components/Admin/Sort.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import CardBoxModal from '@/Components/CardBoxModal.vue';
|
||||
|
||||
const isModalDangerActive = ref(false);
|
||||
|
||||
const props = defineProps({
|
||||
roles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
filters: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
can: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const flash = computed(() => {
|
||||
// let test = usePage();
|
||||
// console.log(test);
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
search: props.filters.search,
|
||||
});
|
||||
|
||||
const formDelete = useForm({});
|
||||
|
||||
// function destroy(id) {
|
||||
// const destroy = async (id) => {
|
||||
// if (confirm('Are you sure you want to delete?')) {
|
||||
// await formDelete.delete(stardust.route('role.destroy', [id]));
|
||||
// }
|
||||
// };
|
||||
|
||||
const destroy = (id, e) => {
|
||||
console.log(id);
|
||||
isModalDangerActive.value = true;
|
||||
};
|
||||
|
||||
const onConfirm = async () => {
|
||||
await ormDelete.delete(stardust.route('role.destroy', [id]));
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
console.log('cancel');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CardBoxModal
|
||||
v-model="isModalDangerActive"
|
||||
large-title="Please confirm"
|
||||
button="danger"
|
||||
button-label="Delete"
|
||||
has-cancel
|
||||
v-on:confirm="onConfirm"
|
||||
v-on:cancel="onCancel"
|
||||
>
|
||||
<p>Lorem ipsum dolor sit amet <b>adipiscing elit</b></p>
|
||||
<p>This is sample modal</p>
|
||||
</CardBoxModal>
|
||||
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Roles" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Roles" main>
|
||||
<BaseButton
|
||||
v-if="can.create"
|
||||
:route-name="stardust.route('role.create')"
|
||||
:icon="mdiPlus"
|
||||
label="Add"
|
||||
color="info"
|
||||
rounded-full
|
||||
small
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
</NotificationBar>
|
||||
<CardBox class="mb-6" has-table>
|
||||
<!-- <form @submit.prevent="form.get(stardust.route('role.index'))">
|
||||
<div class="py-2 flex">
|
||||
<div class="flex pl-4">
|
||||
<input
|
||||
type="search"
|
||||
v-model="form.search"
|
||||
class="rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<BaseButton
|
||||
label="Search"
|
||||
type="submit"
|
||||
color="info"
|
||||
class="ml-4 inline-flex items-center px-4 py-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form> -->
|
||||
</CardBox>
|
||||
<CardBox class="mb-6" has-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Sort label="Name" attribute="name" />
|
||||
</th>
|
||||
<th>
|
||||
<Sort label="Description" attribute="description" />
|
||||
</th>
|
||||
<th v-if="can.edit || can.delete">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="role in roles" :key="role.id">
|
||||
<td data-label="Name">
|
||||
<Link
|
||||
:href="stardust.route('role.show', [role.id])"
|
||||
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400"
|
||||
>
|
||||
{{ role.name }}
|
||||
</Link>
|
||||
</td>
|
||||
<td data-label="Description">
|
||||
{{ role.description }}
|
||||
</td>
|
||||
<td v-if="can.edit || can.delete" class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton
|
||||
v-if="can.edit"
|
||||
:route-name="stardust.route('role.edit', [role.id])"
|
||||
color="info"
|
||||
:icon="mdiSquareEditOutline"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="can.delete"
|
||||
color="danger"
|
||||
:icon="mdiTrashCan"
|
||||
small
|
||||
@click="($event) => destroy(role.id, $event)"
|
||||
/>
|
||||
<!-- <BaseButton v-if="can.delete" color="danger" :icon="mdiTrashCan" small @click="isModalDangerActive = true" /> -->
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- <div class="py-4">
|
||||
<Pagination v-bind:data="roles.meta" />
|
||||
</div> -->
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
79
resources/js/Pages/Admin/Role/Show.vue
Normal file
79
resources/js/Pages/Admin/Role/Show.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline,
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import BaseButton from "@/Components/BaseButton.vue"
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
role: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
permissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
roleHasPermissions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="View role" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="View role" main>
|
||||
<BaseButton :route-name="stardust.route('role.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
|
||||
rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox class="mb-6">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="p-4 pl-8 text-slate-500 dark:text-slate-400 hidden lg:block">
|
||||
Name
|
||||
</td>
|
||||
<td data-label="Name">
|
||||
{{ role.name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4 pl-8 text-slate-500 dark:text-slate-400 hidden lg:block">
|
||||
Name
|
||||
</td>
|
||||
<td data-label="Description">
|
||||
{{ role.description }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
">
|
||||
Created
|
||||
</td>
|
||||
<td data-label="Created">
|
||||
<!-- {{ new Date(role.created_at).toLocaleString() }} -->
|
||||
{{ role.created_at }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
52
resources/js/Pages/Admin/Settings.vue
Normal file
52
resources/js/Pages/Admin/Settings.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
|
||||
import {
|
||||
mdiContrastCircle,
|
||||
mdiInformation,
|
||||
mdiCheckCircle,
|
||||
mdiAlert,
|
||||
mdiAlertCircle,
|
||||
mdiOpenInNew,
|
||||
mdiClose,
|
||||
mdiReload,
|
||||
mdiTrendingUp,
|
||||
} from '@mdi/js';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
// import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
// import NotificationBar from "@/components/NotificationBar.vue";
|
||||
// import BaseDivider from "@/components/BaseDivider.vue";
|
||||
// import CardBoxModal from "@/components/CardBoxModal.vue";
|
||||
import SectionTitle from "@/Components/SectionTitle.vue";
|
||||
// import FormField from "@/components/FormField.vue";
|
||||
// import FormCheckRadioGroup from "@/components/FormCheckRadioGroup.vue";
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue";
|
||||
// import SectionTitleLineWithButton from "@/components/SectionTitleLineWithButton.vue";
|
||||
// import CardBoxComponentEmpty from "@/components/CardBoxComponentEmpty.vue";
|
||||
// import CardBoxComponentTitle from "@/components/CardBoxComponentTitle.vue";
|
||||
// import PillTag from "@/components/PillTag.vue";
|
||||
|
||||
// const modalOneActive = ref(false);
|
||||
// const modalTwoActive = ref(false);
|
||||
// const modalThreeActive = ref(false);
|
||||
// const notificationSettingsModel = ref([]);
|
||||
|
||||
const styleService = StyleService();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionTitle first>Dark mode</SectionTitle>
|
||||
|
||||
<SectionMain>
|
||||
<CardBox class="md:w-7/12 lg:w-5/12 xl:w-4/12 shadow-2xl md:mx-auto">
|
||||
<div class="text-center py-24 lg:py-12 text-gray-500 dark:text-slate-400">
|
||||
<BaseButton label="Toggle" color="contrast" @click="styleService.setDarkMode()" />
|
||||
</div>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
151
resources/js/Pages/Admin/User/AccountInfo.vue
Normal file
151
resources/js/Pages/Admin/User/AccountInfo.vue
Normal file
|
@ -0,0 +1,151 @@
|
|||
<script setup>
|
||||
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
|
||||
import { Head, Link, useForm, router } from '@inertiajs/vue3';
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiAccountCircle,
|
||||
mdiLock,
|
||||
mdiMail,
|
||||
mdiAsterisk,
|
||||
mdiFormTextboxPassword,
|
||||
mdiArrowLeftBoldOutline,
|
||||
mdiAlertBoxOutline,
|
||||
} from '@mdi/js';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
const props = defineProps({
|
||||
// user will be returned from controller action
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const profileForm = useForm({
|
||||
login: props.user.login,
|
||||
email: props.user.email,
|
||||
});
|
||||
const profileSubmit = async () => {
|
||||
await router.post(stardust.route('admin.account.info.store', [props.user.id]), profileForm);
|
||||
};
|
||||
|
||||
const passwordForm = useForm({
|
||||
old_password: null,
|
||||
new_password: null,
|
||||
confirm_password: null,
|
||||
});
|
||||
const passwordSubmit = async () => {
|
||||
await router.post(stardust.route('admin.account.info.store'), passwordForm, {
|
||||
preserveScroll: true,
|
||||
onSuccess: (resp) => {
|
||||
console.log(resp);
|
||||
passwordForm.reset();
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccount" title="Profile" main>
|
||||
<BaseButton :route-name="stardust.route('dashboard')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
|
||||
rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<NotificationBar v-if="$page.props.flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ $page.props.flash.message }}
|
||||
</NotificationBar>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
<!-- password form -->
|
||||
<!-- <CardBox title="Edit Profile" :icon="mdiAccountCircle" form @submit.prevent="profileForm.post(route('admin.account.info.store'))"> -->
|
||||
<CardBox title="Edit Profile" :icon="mdiAccountCircle" form @submit.prevent="profileSubmit()">
|
||||
<FormField label="Login" help="Required. Your login name" :class="{ 'text-red-400': errors.login }">
|
||||
<FormControl v-model="profileForm.login" v-bind:icon="mdiAccount" name="login" required :error="errors.login">
|
||||
<div class="text-red-400 text-sm" v-if="errors.login">
|
||||
{{ errors.login }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
<FormField label="Email" help="Required. Your e-mail" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="profileForm.email" :icon="mdiMail" type="email" name="email" required
|
||||
:error="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email">
|
||||
{{ errors.email }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton color="info" type="submit" label="Submit" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
|
||||
<!-- password form -->
|
||||
<!-- <CardBox title="Change Password" :icon="mdiLock" form @submit.prevent="passwordForm.post(route('admin.account.password.store'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => passwordForm.reset(),
|
||||
})
|
||||
"> -->
|
||||
<CardBox title="Change Password" :icon="mdiLock" form @submit.prevent="passwordSubmit()">
|
||||
<FormField label="Current password" help="Required. Your current password"
|
||||
:class="{ 'text-red-400': passwordForm.errors.old_password }">
|
||||
<FormControl v-model="passwordForm.old_password" :icon="mdiAsterisk" name="old_password" type="password"
|
||||
required :error="passwordForm.errors.old_password">
|
||||
<div class="text-red-400 text-sm" v-if="passwordForm.errors.old_password">
|
||||
{{ passwordForm.errors.old_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="New password" help="Required. New password" :class="{ 'text-red-400': passwordForm.errors.new_password }">
|
||||
<FormControl v-model="passwordForm.new_password" :icon="mdiFormTextboxPassword" name="new_password"
|
||||
type="password" required :error="passwordForm.errors.new_password">
|
||||
<div class="text-red-400 text-sm" v-if="passwordForm.errors.new_password">
|
||||
{{ passwordForm.errors.new_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Confirm password" help="Required. New password one more time"
|
||||
:class="{ 'text-red-400': passwordForm.errors.confirm_password }">
|
||||
<FormControl v-model="passwordForm.confirm_password" :icon="mdiFormTextboxPassword" name="confirm_password"
|
||||
type="password" required :error="passwordForm.errors.confirm_password">
|
||||
<div class="text-red-400 text-sm" v-if="passwordForm.errors.confirm_password">
|
||||
{{ passwordForm.errors.confirm_password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
|
||||
</div>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
110
resources/js/Pages/Admin/User/Create.vue
Normal file
110
resources/js/Pages/Admin/User/Create.vue
Normal file
|
@ -0,0 +1,110 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm, router } from '@inertiajs/vue3';
|
||||
import { mdiAccountKey, mdiArrowLeftBoldOutline } from '@mdi/js';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
|
||||
import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
const props = defineProps({
|
||||
roles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
login: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
roles: [],
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
await router.post(stardust.route('user.store'), form);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Add user" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Add user" main>
|
||||
<BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back"
|
||||
color="white" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- @submit.prevent="form.post(stardust.route('user.store'))" -->
|
||||
<CardBox form @submit.prevent="submit()">
|
||||
<FormField label="Login" :class="{ 'text-red-400': errors.name }">
|
||||
<FormControl v-model="form.login" type="text" placeholder="Enter Login" :errors="errors.login">
|
||||
<div class="text-red-400 text-sm" v-if="errors.login && Array.isArray(errors.login)">
|
||||
<!-- {{ errors.login }} -->
|
||||
{{ errors.login.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Email" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="form.email" type="text" placeholder="Enter Email" :errors="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email && Array.isArray(errors.email)">
|
||||
<!-- {{ errors.email }} -->
|
||||
{{ errors.email.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Password" :class="{ 'text-red-400': errors.password }">
|
||||
<FormControl v-model="form.password" type="password" placeholder="Enter Password"
|
||||
:errors="errors.password">
|
||||
<div class="text-red-400 text-sm" v-if="errors.password && Array.isArray(errors.password)">
|
||||
<!-- {{ errors.password }} -->
|
||||
{{ errors.password.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Password Confirmation" :class="{ 'text-red-400': errors.password_confirmation }">
|
||||
<FormControl v-model="form.password_confirmation" type="password"
|
||||
placeholder="Enter Password Confirmation" :errors="errors.password">
|
||||
<div class="text-red-400 text-sm"
|
||||
v-if="errors.password_confirmation && Array.isArray(errors.password_confirmation)">
|
||||
<!-- {{ errors.password_confirmation }} -->
|
||||
{{ errors.password_confirmation.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="Roles" wrap-body :class="{ 'text-red-400': errors.roles }">
|
||||
<FormCheckRadioGroup v-model="form.roles" name="roles" is-column :options="props.roles" />
|
||||
</FormField>
|
||||
<div class="text-red-400 text-sm" v-if="errors.roles && Array.isArray(errors.roles)">
|
||||
<!-- {{ errors.password_confirmation }} -->
|
||||
{{ errors.roles.join(', ') }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
118
resources/js/Pages/Admin/User/Edit.vue
Normal file
118
resources/js/Pages/Admin/User/Edit.vue
Normal file
|
@ -0,0 +1,118 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm, router } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import FormField from '@/Components/FormField.vue'
|
||||
import FormControl from '@/Components/FormControl.vue'
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue'
|
||||
import BaseDivider from '@/Components/BaseDivider.vue'
|
||||
import BaseButton from '@/Components/BaseButton.vue'
|
||||
import BaseButtons from '@/Components/BaseButtons.vue'
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
roles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
userHasRoles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
_method: 'put',
|
||||
login: props.user.login,
|
||||
email: props.user.email,
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
roles: props.userHasRoles, // fill actual user roles from db
|
||||
})
|
||||
|
||||
const submit = async () => {
|
||||
// await Inertia.post(stardust.route('user.store'), form);
|
||||
await router.put(stardust.route('user.update', [props.user.id]), form);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Update user" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Update user" main>
|
||||
<BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back"
|
||||
color="white" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- <CardBox form @submit.prevent="form.put(stardust.route('user.update', [props.user.id]))"> -->
|
||||
<CardBox form @submit.prevent="submit()">
|
||||
|
||||
<FormField label="Enter Login" :class="{ 'text-red-400': errors.name }">
|
||||
<FormControl v-model="form.login" type="text" placeholder="Name" :errors="errors.login">
|
||||
<div class="text-red-400 text-sm" v-if="errors.login">
|
||||
{{ errors.login }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Enter Email" :class="{ 'text-red-400': errors.email }">
|
||||
<FormControl v-model="form.email" type="text" placeholder="Email" :errors="errors.email">
|
||||
<div class="text-red-400 text-sm" v-if="errors.email && Array.isArray(errors.email)">
|
||||
{{ errors.email.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Password" :class="{ 'text-red-400': errors.password }">
|
||||
<FormControl v-model="form.password" type="password" placeholder="Enter Password" :errors="errors.password">
|
||||
<div class="text-red-400 text-sm" v-if="errors.password">
|
||||
{{ errors.password }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Password Confirmation" :class="{ 'text-red-400': errors.password_confirmation }">
|
||||
<FormControl v-model="form.password_confirmation" type="password" placeholder="Enter Password Confirmation"
|
||||
:errors="errors.password">
|
||||
<div class="text-red-400 text-sm" v-if="errors.password_confirmation && Array.isArray(errors.password_confirmation)">
|
||||
{{ errors.password_confirmation.join(', ') }}
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="Roles" wrap-body :class="{ 'text-red-400': errors.roles }">
|
||||
<FormCheckRadioGroup v-model="form.roles" name="roles" is-column :options="props.roles" />
|
||||
</FormField>
|
||||
<div class="text-red-400 text-sm" v-if="errors.roles && Array.isArray(errors.roles)">
|
||||
<!-- {{ errors.password_confirmation }} -->
|
||||
{{ errors.roles.join(', ') }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing" />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
163
resources/js/Pages/Admin/User/Index.vue
Normal file
163
resources/js/Pages/Admin/User/Index.vue
Normal file
|
@ -0,0 +1,163 @@
|
|||
<script setup>
|
||||
// import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3';
|
||||
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiPlus,
|
||||
mdiSquareEditOutline,
|
||||
mdiTrashCan,
|
||||
mdiAlertBoxOutline,
|
||||
} from '@mdi/js';
|
||||
import { watch, computed } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import Pagination from '@/Components/Admin/Pagination.vue';
|
||||
import Sort from '@/Components/Admin/Sort.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
// import { Vue } from 'vue-facing-decorator';
|
||||
|
||||
const props = defineProps({
|
||||
users: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
filters: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
can: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// }
|
||||
});
|
||||
|
||||
// const search = computed(() => {
|
||||
// return props.filters.search;
|
||||
// });
|
||||
|
||||
const flash = computed(() => {
|
||||
// let test = usePage();
|
||||
// console.log(test);
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
search: props.filters.search,
|
||||
});
|
||||
|
||||
const formDelete = useForm({});
|
||||
|
||||
// async function destroy(id) {
|
||||
const destroy = async (id) => {
|
||||
if (confirm('Are you sure you want to delete?')) {
|
||||
await formDelete.delete(stardust.route('user.destroy', [id]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Users" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="Tethys Users" main>
|
||||
<BaseButton v-if="can.create" :route-name="stardust.route('user.create')" :icon="mdiPlus" label="Add"
|
||||
color="info" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<!-- <label>{{ form.search }}</label> -->
|
||||
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
</NotificationBar>
|
||||
<!-- <NotificationBar color="success" :icon="mdiAlertBoxOutline">{{ users.meta }}</NotificationBar> -->
|
||||
<CardBox class="mb-6" has-table>
|
||||
<form @submit.prevent="form.get(stardust.route('user.index'))">
|
||||
<div class="py-2 flex">
|
||||
<div class="flex pl-4">
|
||||
<input type="search" v-model="form.search"
|
||||
class="rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
placeholder="Search" />
|
||||
<BaseButton label="Search" type="submit" color="info"
|
||||
class="ml-4 inline-flex items-center px-4 py-2" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardBox>
|
||||
<!-- table -->
|
||||
<CardBox class="mb-6" has-table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Sort label="Login" attribute="login" :search="form.search" />
|
||||
</th>
|
||||
<th>
|
||||
<Sort label="Email" attribute="email" :search="form.search" />
|
||||
</th>
|
||||
<th v-if="can.edit || can.delete">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr v-for="user in users.data" :key="user.id">
|
||||
<td data-label="Login">
|
||||
<Link v-bind:href="stardust.route('user.show', [user.id])"
|
||||
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400">
|
||||
{{ user.login }}
|
||||
</Link>
|
||||
<!-- {{ user.id }} -->
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
{{ user.email }}
|
||||
</td>
|
||||
<td v-if="can.edit || can.delete" class="before:hidden lg:w-1 whitespace-nowrap">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton
|
||||
v-if="can.edit"
|
||||
:route-name="stardust.route('user.edit', [user.id])"
|
||||
color="info"
|
||||
:icon="mdiSquareEditOutline"
|
||||
small
|
||||
/>
|
||||
<BaseButton
|
||||
v-if="can.delete"
|
||||
color="danger"
|
||||
:icon="mdiTrashCan"
|
||||
small
|
||||
@click="destroy(user.id)"
|
||||
/>
|
||||
</BaseButtons>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="py-4">
|
||||
<Pagination v-bind:data="users.meta" />
|
||||
<!-- <ul>
|
||||
<li>
|
||||
<a href="{{ users.page == 1 ? '#' : '?page=' + (users.page - 1) }}">Previous</a>
|
||||
</li>
|
||||
@each(page in ???)
|
||||
<li>
|
||||
<a href="?page={{ page }}">{{ page }}</a>
|
||||
</li>
|
||||
@endeach
|
||||
<li>
|
||||
<a href="{{ users.lastPage == users.page ? '#' : '?page=' + (users.page + 1) }}">Next</a>
|
||||
</li>
|
||||
</ul> -->
|
||||
</div>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
92
resources/js/Pages/Admin/User/Show.vue
Normal file
92
resources/js/Pages/Admin/User/Show.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<script setup>
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3"
|
||||
import {
|
||||
mdiAccountKey,
|
||||
mdiArrowLeftBoldOutline,
|
||||
} from "@mdi/js"
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue"
|
||||
import SectionMain from "@/Components/SectionMain.vue"
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue"
|
||||
import CardBox from "@/Components/CardBox.vue"
|
||||
import BaseButton from "@/Components/BaseButton.vue"
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
roles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
userHasRoles: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated :user="user">
|
||||
|
||||
<Head title="View user" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccountKey" title="View user" main>
|
||||
<BaseButton :route-name="stardust.route('user.index')" :icon="mdiArrowLeftBoldOutline" label="Back" color="white"
|
||||
rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox class="mb-6">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
">
|
||||
Login
|
||||
</td>
|
||||
<td data-label="Login">
|
||||
{{ user.login }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
">
|
||||
Email
|
||||
</td>
|
||||
<td data-label="Email">
|
||||
{{ user.email }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="
|
||||
p-4
|
||||
pl-8
|
||||
text-slate-500
|
||||
dark:text-slate-400
|
||||
hidden
|
||||
lg:block
|
||||
">
|
||||
Created
|
||||
</td>
|
||||
<td data-label="Created">
|
||||
{{ new Date(user.created_at).toLocaleString() }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
87
resources/js/Pages/App.vue
Normal file
87
resources/js/Pages/App.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div>
|
||||
<Link href="/app/login">Login</Link>
|
||||
<h1 class="text-red-500">Welcome, {{ testing }}</h1>
|
||||
<n-button>Testing</n-button>
|
||||
<n-input v-bind:value="testing"></n-input>
|
||||
|
||||
<div class="features">
|
||||
<ul>
|
||||
<li v-for="user in users">
|
||||
<span>{{ user.login }}</span>
|
||||
<span>{{ user.email }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- <script>
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
import DefaultLayout from '@/Layouts/Default.vue';
|
||||
import { NInput, NButton } from 'naive-ui';
|
||||
|
||||
export default {
|
||||
layout: DefaultLayout,
|
||||
props: {
|
||||
testing: String,
|
||||
users: Array,
|
||||
},
|
||||
|
||||
components: {
|
||||
Link,
|
||||
NInput,
|
||||
NButton,
|
||||
},
|
||||
};
|
||||
</script> -->
|
||||
|
||||
<!-- <script lang="js">
|
||||
import { Link } from '@inertiajs/vue3'
|
||||
import DefaultLayout from '../Layouts/Default.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
testing: String,
|
||||
},
|
||||
|
||||
components: {
|
||||
Link,
|
||||
DefaultLayout
|
||||
}
|
||||
}
|
||||
</script> -->
|
||||
|
||||
<script lang ="ts">
|
||||
import { Component, Vue, Prop } from 'vue-facing-decorator';
|
||||
import IUser from "App/Models/User";
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import DefaultLayout from '@/Layouts/Default.vue';
|
||||
import { NInput, NButton } from 'naive-ui';
|
||||
|
||||
@Component({
|
||||
options: {
|
||||
layout: DefaultLayout,
|
||||
},
|
||||
name: 'AppComponent',
|
||||
components: {
|
||||
Link,
|
||||
NInput,
|
||||
NButton,
|
||||
},
|
||||
})
|
||||
export default class AppComponent extends Vue {
|
||||
// Component Property
|
||||
@Prop({
|
||||
type: String,
|
||||
default: () => (""),
|
||||
})
|
||||
testing: string;
|
||||
|
||||
@Prop({
|
||||
type: Array,
|
||||
default: () => ([]),
|
||||
})
|
||||
users: Array<IUser>;
|
||||
}
|
||||
</script>
|
145
resources/js/Pages/Auth/Login.vue
Normal file
145
resources/js/Pages/Auth/Login.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<!-- <template>
|
||||
<div>
|
||||
<Link href="/app">Home</Link>
|
||||
<br />
|
||||
<h1>Login</h1>
|
||||
<Head>
|
||||
<title>About - My app</title>
|
||||
<meta
|
||||
head-key="description"
|
||||
name="description"
|
||||
content="This is a page specific description"
|
||||
/>
|
||||
</Head>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AuthLayout from '@/Layouts/Auth.vue';
|
||||
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
|
||||
export default {
|
||||
layout: AuthLayout,
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
// import { Head, Link } from '@inertiajs/vue3';
|
||||
import { Head, Link } from '@inertiajs/inertia-vue3'
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
</script> -->
|
||||
|
||||
<template>
|
||||
<LayoutGuest>
|
||||
|
||||
<Head title="Login" />
|
||||
|
||||
<SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'">
|
||||
<CardBox :class="cardClass" form @submit.prevent="submit">
|
||||
<FormValidationErrors v-bind:errors="errors" />
|
||||
|
||||
<NotificationBarInCard v-if="status" color="info">
|
||||
{{ status }}
|
||||
</NotificationBarInCard>
|
||||
|
||||
<FormField label="Email" label-for="email" help="Please enter your email">
|
||||
<FormControl v-model="form.email" :icon="mdiAccount" id="email" autocomplete="email" type="email"
|
||||
required />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Password" label-for="password" help="Please enter your password">
|
||||
<FormControl v-model="form.password" :icon="mdiAsterisk" type="password" id="password"
|
||||
autocomplete="current-password" required />
|
||||
</FormField>
|
||||
|
||||
<FormCheckRadioGroup v-model="form.remember" name="remember" :options="{ remember: 'Remember' }" />
|
||||
|
||||
<!-- <NotificationBar v-if="flash && flash.message" color="warning" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4"
|
||||
</NotificationBar> -->
|
||||
<div v-if="flash && flash.message" class="flex flex-col mt-6 animate-fade-in">
|
||||
<div class="bg-yellow-500 border-l-4 border-orange-400 text-white p-4" role="alert">
|
||||
<p class="font-bold">Be Warned</p>
|
||||
<p>{{ flash.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<!-- buttons -->
|
||||
<BaseLevel>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Login" :class="{ 'opacity-25': form.processing }"
|
||||
v-bind:disabled="form.processing" />
|
||||
<!-- <BaseButton v-if="canResetPassword" :route-name="route('password.request')" color="info" outline
|
||||
label="Remind" /> -->
|
||||
</BaseButtons>
|
||||
<Link :href="stardust.route('app.register.show')"> Register </Link>
|
||||
</BaseLevel>
|
||||
</CardBox>
|
||||
</SectionFullScreen>
|
||||
</LayoutGuest>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useForm, Head, Link } from '@inertiajs/vue3';
|
||||
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
|
||||
import { mdiAccount, mdiAsterisk } from '@mdi/js';
|
||||
import LayoutGuest from '@/Layouts/LayoutGuest.vue';
|
||||
import SectionFullScreen from '@/Components/SectionFullScreen.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
|
||||
import FormField from '@/Components/FormField.vue';
|
||||
import FormControl from '@/Components/FormControl.vue';
|
||||
import BaseDivider from '@/Components/BaseDivider.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
|
||||
import NotificationBarInCard from '@/Components/NotificationBarInCard.vue';
|
||||
import BaseLevel from '@/Components/BaseLevel.vue';
|
||||
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import { computed } from 'vue';
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
|
||||
// interface IErrorMessage {
|
||||
// [key: string]: Array<string>;
|
||||
// }
|
||||
const flash = computed(() => {
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
canResetPassword: Boolean,
|
||||
status: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: [],
|
||||
});
|
||||
|
||||
const submit = async() => {
|
||||
await form
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
remember: form.remember && form.remember.length ? 'on' : '',
|
||||
}))
|
||||
.post(stardust.route('login.store'), {
|
||||
// onFinish: () => {
|
||||
// form.reset('password');
|
||||
// }
|
||||
});
|
||||
};
|
||||
</script>
|
59
resources/js/Pages/Auth/Register.vue
Normal file
59
resources/js/Pages/Auth/Register.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- <Link href="/app">Home</Link>
|
||||
<br /> -->
|
||||
<h1>Register</h1>
|
||||
<form @submit.prevent="submit()" class="max-w-sm">
|
||||
<!-- <form @submit.prevent="form.post('/app/register')" class="max-w-sm"> -->
|
||||
<!-- <n-input type="email" v-model:value="form.email" placeholder="email" class="mb-3" />
|
||||
<n-input type="password" v-model:value="form.password" placeholder="Password" class="mb-3" /> -->
|
||||
<form-input v-bind:label="'Emai22l'" v-bind:type="'email'" v-model="form.email" />
|
||||
<form-input v-bind:label="'Password'" v-bind:type="'password'" v-model="form.password" />
|
||||
|
||||
<n-button attr-type="submit"> Register </n-button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import AuthLayout from '../../Layouts/Auth.vue';
|
||||
import AuthLayout from '@/Layouts/Auth.vue';
|
||||
import { reactive } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import { NButton, NInput } from 'naive-ui';
|
||||
// import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import FormInput from '@/Components/FormInput.vue'
|
||||
|
||||
export default {
|
||||
layout: AuthLayout,
|
||||
|
||||
components: {
|
||||
NButton,
|
||||
// NInput,
|
||||
FormInput
|
||||
},
|
||||
|
||||
setup() {
|
||||
// const form = useForm({
|
||||
// email: '',
|
||||
// password: ''
|
||||
// });
|
||||
const form = reactive({
|
||||
email: null,
|
||||
password: null,
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
await Inertia.post('/app/register', form);
|
||||
};
|
||||
|
||||
return { form, submit };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- <script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
// import DefaultLayout from '../../Layouts/Default.vue';
|
||||
</script> -->
|
123
resources/js/Pages/Dashboard.vue
Normal file
123
resources/js/Pages/Dashboard.vue
Normal file
|
@ -0,0 +1,123 @@
|
|||
<script setup>
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import {
|
||||
mdiAccountMultiple,
|
||||
mdiCartOutline,
|
||||
mdiDatabaseOutline,
|
||||
mdiChartTimelineVariant,
|
||||
mdiFinance,
|
||||
mdiMonitorCellphone,
|
||||
mdiReload,
|
||||
mdiGithub,
|
||||
mdiChartPie,
|
||||
} from '@mdi/js';
|
||||
import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
|
||||
import * as chartConfig from '@/Components/Charts/chart.config.js';
|
||||
import LineChart from '@/Components/Charts/LineChart.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import CardBoxWidget from '@/Components/CardBoxWidget.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import TableSampleClients from '@/Components/TableSampleClients.vue';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import CardBoxTransaction from '@/Components/CardBoxTransaction.vue';
|
||||
import CardBoxClient from '@/Components/CardBoxClient.vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import SectionBannerStarOnGitHub from '@/Components/SectionBannerStarOnGitea.vue';
|
||||
const chartData = ref(null);
|
||||
const fillChartData = () => {
|
||||
chartData.value = chartConfig.sampleChartData();
|
||||
};
|
||||
onMounted(() => {
|
||||
fillChartData();
|
||||
});
|
||||
const mainService = MainService();
|
||||
/* Fetch sample data */
|
||||
mainService.fetch('clients');
|
||||
mainService.fetch('history');
|
||||
|
||||
mainService.fetchApi('authors');
|
||||
mainService.fetchApi('datasets');
|
||||
|
||||
const clientBarItems = computed(() => mainService.clients.slice(0, 4));
|
||||
const transactionBarItems = computed(() => mainService.history);
|
||||
|
||||
const authorBarItems = computed(() => mainService.authors.slice(0, 4));
|
||||
const authors = computed(() => mainService.authors);
|
||||
const datasets = computed(() => mainService.datasets);
|
||||
// const props = defineProps({
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// }
|
||||
// });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Dashboard" />
|
||||
|
||||
<!-- <section class="p-6" v-bind:class="containerMaxW"> -->
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Overview" main>
|
||||
<BaseButton href="https://gitea.geologie.ac.at/geolba/tethys" target="_blank" :icon="mdiGithub"
|
||||
label="Star on Gitea" color="contrast" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6">
|
||||
<CardBoxWidget trend="12%" trend-type="up" color="text-emerald-500" :icon="mdiAccountMultiple"
|
||||
:number="authors.length" label="Authors" />
|
||||
<CardBoxWidget trend="193" trend-type="info" color="text-blue-500" :icon="mdiDatabaseOutline" :number="datasets.length"
|
||||
label="Publications" />
|
||||
<!-- <CardBoxWidget trend="193" trend-type="info" color="text-blue-500" :icon="mdiCartOutline" :number="datasets.length"
|
||||
prefix="$" label="Publications" /> -->
|
||||
<CardBoxWidget trend="Overflow" trend-type="alert" color="text-red-500" :icon="mdiChartTimelineVariant"
|
||||
:number="256" suffix="%" label="Performance" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<div class="flex flex-col justify-between">
|
||||
<CardBoxClient
|
||||
v-for="client in authorBarItems"
|
||||
:key="client.id"
|
||||
:name="client.name"
|
||||
:email="client.email"
|
||||
:date="client.created_at"
|
||||
:text="client.datasetCount"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col justify-between">
|
||||
<CardBoxTransaction v-for="(transaction, index) in transactionBarItems" :key="index"
|
||||
:amount="transaction.amount" :date="transaction.date" :business="transaction.business"
|
||||
:type="transaction.type" :name="transaction.name" :account="transaction.account" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SectionBannerStarOnGitHub />
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview (to do publications per year)" />
|
||||
<CardBox title="Performance" :icon="mdiFinance" :header-icon="mdiReload" class="mb-6"
|
||||
@header-icon-click="fillChartData">
|
||||
<div v-if="chartData">
|
||||
<line-chart :data="chartData" class="h-96" />
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<SectionTitleLineWithButton :icon="mdiAccountMultiple" title="Submitters (to do)" />
|
||||
|
||||
<!-- <NotificationBar color="info" :icon="mdiMonitorCellphone">
|
||||
<b>Responsive table.</b> Collapses on mobile
|
||||
</NotificationBar> -->
|
||||
|
||||
<CardBox :icon="mdiMonitorCellphone" title="Responsive table" has-table>
|
||||
<TableSampleClients />
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<!-- </section> -->
|
||||
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
79
resources/js/Pages/Error.vue
Normal file
79
resources/js/Pages/Error.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<!-- https://github.com/inertiajs/inertia-laravel/issues/56 -->
|
||||
<script setup>
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import { computed } from 'vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import { mdiChartTimelineVariant, mdiGithub } from '@mdi/js';
|
||||
|
||||
const props = defineProps({
|
||||
status: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const title = computed(() => {
|
||||
return {
|
||||
503: '503: Service Unavailable',
|
||||
500: '500: Server Error',
|
||||
404: '404: Page Not Found',
|
||||
403: '404: Forbidden',
|
||||
401: '401: Unauthorized',
|
||||
}[props.status];
|
||||
});
|
||||
|
||||
const description = computed(() => {
|
||||
return {
|
||||
503: 'Sorry, we are doing some maintenance. Please check back soon.',
|
||||
500: 'Whoops, something went wrong on our servers.',
|
||||
404: 'Sorry, the page you are looking for could not be found.',
|
||||
403: 'Sorry, you are forbidden from accessing this page.',
|
||||
401: 'Sorry, you are forbidden from accessing this page.',
|
||||
}[props.status];
|
||||
});
|
||||
// export default {
|
||||
// // layout: LayoutAuthenticated,
|
||||
// props: {
|
||||
// status: Number,
|
||||
// },
|
||||
// computed: {
|
||||
// title() {
|
||||
// return {
|
||||
// 503: '503: Service Unavailable',
|
||||
// 500: '500: Server Error',
|
||||
// 404: '404: Page Not Found',
|
||||
// 403: '404: Forbidden',
|
||||
// 401: '404: Forbidden',
|
||||
// }[this.status];
|
||||
// },
|
||||
// description() {
|
||||
// return {
|
||||
// 503: 'Sorry, we are doing some maintenance. Please check back soon.',
|
||||
// 500: 'Whoops, something went wrong on our servers.',
|
||||
// 404: 'Sorry, the page you are looking for could not be found.',
|
||||
// 403: 'Sorry, you are forbidden from accessing this page.',
|
||||
// 401: 'Sorry, you are forbidden from accessing this page.',
|
||||
// }[this.status];
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton v-bind:icon="mdiChartTimelineVariant" title="Error" main>
|
||||
<BaseButton href="https://gitea.geologie.ac.at/geolba/tethys" target="_blank" :icon="mdiGithub"
|
||||
label="Star on Gitea" color="contrast" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
<h1>{{ title }}</h1>
|
||||
<div>{{ props.message }}</div>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
101
resources/js/Pages/ProfileView.vue
Normal file
101
resources/js/Pages/ProfileView.vue
Normal file
|
@ -0,0 +1,101 @@
|
|||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { MainService } from "@/Stores/main";
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiMail,
|
||||
mdiAsterisk,
|
||||
mdiFormTextboxPassword,
|
||||
mdiGithub,
|
||||
} from "@mdi/js";
|
||||
import SectionMain from "@/Components/SectionMain.vue";
|
||||
import CardBox from "@/Components/CardBox.vue";
|
||||
import BaseDivider from "@/Components/BaseDivider.vue";
|
||||
import FormField from "@/Components/FormField.vue";
|
||||
import FormControl from "@/Components/FormControl.vue";
|
||||
import FormFilePicker from "@/Components/FormFilePicker.vue";
|
||||
import BaseButton from "@/Components/BaseButton.vue";
|
||||
import BaseButtons from "@/Components/BaseButtons.vue";
|
||||
// import UserCard from "@/Components/UserCard.vue";
|
||||
import LayoutAuthenticated from "@/Layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLineWithButton from "@/Components/SectionTitleLineWithButton.vue";
|
||||
const mainService = MainService();
|
||||
const profileForm = reactive({
|
||||
name: mainService.userName,
|
||||
email: mainService.userEmail,
|
||||
});
|
||||
const passwordForm = reactive({
|
||||
password_current: "",
|
||||
password: "",
|
||||
password_confirmation: "",
|
||||
});
|
||||
const submitProfile = () => {
|
||||
mainService.setUser(profileForm);
|
||||
};
|
||||
const submitPass = () => {
|
||||
//
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccount" title="Profile" main>
|
||||
<BaseButton href="https://github.com/justboil/admin-one-vue-tailwind" target="_blank" :icon="mdiGithub"
|
||||
label="Star on GitHub" color="contrast" rounded-full small />
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
<UserCard class="mb-6" />
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<CardBox is-form @submit.prevent="submitProfile">
|
||||
<FormField label="Avatar" help="Max 500kb">
|
||||
<FormFilePicker label="Upload" />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Name" help="Required. Your name">
|
||||
<FormControl v-model="profileForm.name" :icon="mdiAccount" name="username" required
|
||||
autocomplete="username" />
|
||||
</FormField>
|
||||
<FormField label="E-mail" help="Required. Your e-mail">
|
||||
<FormControl v-model="profileForm.email" :icon="mdiMail" type="email" name="email" required
|
||||
autocomplete="email" />
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton color="info" type="submit" label="Submit" />
|
||||
<BaseButton color="info" label="Options" outline />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
|
||||
<CardBox is-form @submit.prevent="submitPass">
|
||||
<FormField label="Current password" help="Required. Your current password">
|
||||
<FormControl v-model="passwordForm.password_current" :icon="mdiAsterisk" name="password_current"
|
||||
type="password" required autocomplete="current-password" />
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<FormField label="New password" help="Required. New password">
|
||||
<FormControl v-model="passwordForm.password" :icon="mdiFormTextboxPassword" name="password"
|
||||
type="password" required autocomplete="new-password" />
|
||||
</FormField>
|
||||
|
||||
<FormField label="Confirm password" help="Required. New password one more time">
|
||||
<FormControl v-model="passwordForm.password_confirmation" :icon="mdiFormTextboxPassword"
|
||||
name="password_confirmation" type="password" required autocomplete="new-password" />
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" />
|
||||
<BaseButton color="info" label="Options" outline />
|
||||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
70
resources/js/Pages/register-view/register-view-component.ts
Normal file
70
resources/js/Pages/register-view/register-view-component.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Component, Vue, Prop } from 'vue-facing-decorator';
|
||||
import AuthLayout from '@/Layouts/Auth.vue';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
import { NButton } from 'naive-ui';
|
||||
import { useForm, InertiaForm, router } from '@inertiajs/vue3';
|
||||
import FormInput from '@/Components/FormInput.vue'; // @/Components/FormInput.vue'
|
||||
// import { defineComponent, reactive } from 'vue';
|
||||
|
||||
// export default defineComponent({
|
||||
// layout: AuthLayout,
|
||||
|
||||
// components: {
|
||||
// NButton,
|
||||
// FormInput,
|
||||
// },
|
||||
|
||||
// setup() {
|
||||
// // const form = useForm({
|
||||
// // email: '',
|
||||
// // password: ''
|
||||
// // });
|
||||
// const form = reactive({
|
||||
// email: null,
|
||||
// password: null,
|
||||
// });
|
||||
|
||||
// const submit = async () => {
|
||||
// await Inertia.post('/app/register', form);
|
||||
// };
|
||||
|
||||
// return { form, submit };
|
||||
// },
|
||||
// });
|
||||
|
||||
export interface IErrorMessage {
|
||||
[key: string]: Array<string>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
options: {
|
||||
layout: AuthLayout,
|
||||
},
|
||||
name: 'RegisterViewComponent',
|
||||
components: {
|
||||
NButton,
|
||||
FormInput,
|
||||
},
|
||||
})
|
||||
export default class RegisterViewComponent extends Vue {
|
||||
// Component Property
|
||||
@Prop({
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
})
|
||||
errors: IErrorMessage;
|
||||
|
||||
// Data Property
|
||||
form: InertiaForm<any> = useForm({
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
results: Array<any> = [];
|
||||
|
||||
// Component method
|
||||
public async submit(): Promise<void> {
|
||||
// await Inertia.post('/app/register', this.form);
|
||||
await router.post('/app/register', this.form);
|
||||
}
|
||||
}
|
128
resources/js/Pages/register-view/register-view-component.vue
Normal file
128
resources/js/Pages/register-view/register-view-component.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- <Link href="/app">Home</Link>
|
||||
<br /> -->
|
||||
<h1>Register</h1>
|
||||
<form v-on:submit.prevent="submit()" class="max-w-sm">
|
||||
<!-- <form @submit.prevent="form.post('/app/register')" class="max-w-sm"> -->
|
||||
<!-- <n-input type="email" v-model:value="form.email" placeholder="email" class="mb-3" /> -->
|
||||
<!-- <n-input type="password" v-model:value="form.password" placeholder="Password" class="mb-3" /> -->
|
||||
<!-- <n-input v-bind:label="'Emai22l'" v-model:value="form.email" />
|
||||
<n-input v-bind:label="'Password'" v-bind:type="'password'" v-model:value="form.password" /> -->
|
||||
<!-- <form-input v-bind:label="'Password'" v-bind:type="'password'" v-model="form.password" /> -->
|
||||
|
||||
<form-input v-bind:label="'Emai22l'" v-bind:type="'email'" v-model="form.email" :errors="errors.email" />
|
||||
<form-input v-bind:label="'Password'" v-bind:type="'password'" v-model="form.password" :errors="errors.password"/>
|
||||
|
||||
<n-button attr-type="submit"> Register </n-button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import RegisterViewComponent from "@/Pages/register-view/register-view-component";
|
||||
// import RegisterViewComponent from "./register-view-component";
|
||||
// const test = RegisterViewComponent;
|
||||
export default RegisterViewComponent;
|
||||
|
||||
|
||||
// import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
// import AuthLayout from '@/Layouts/Auth.vue';
|
||||
// // import { reactive } from 'vue';
|
||||
// import { Inertia } from '@inertiajs/inertia';
|
||||
// import { NButton } from 'naive-ui';
|
||||
// import FormInput from '@/Components/FormInput.vue';// @/Components/FormInput.vue'
|
||||
// // import { defineComponent } from 'vue';
|
||||
|
||||
|
||||
|
||||
// export default {
|
||||
// layout: AuthLayout,
|
||||
|
||||
// components: {
|
||||
// // NButton,
|
||||
// // NInput,
|
||||
// FormInput
|
||||
// },
|
||||
|
||||
// setup() {
|
||||
// // const form = useForm({
|
||||
// // email: '',
|
||||
// // password: ''
|
||||
// // });
|
||||
// const form = reactive({
|
||||
// email: null,
|
||||
// password: null,
|
||||
// });
|
||||
|
||||
// const submit = async () => {
|
||||
// await Inertia.post('/app/register', form);
|
||||
// };
|
||||
|
||||
// return { form, submit };
|
||||
// },
|
||||
// };
|
||||
|
||||
// export default defineComponent({
|
||||
// layout: AuthLayout,
|
||||
|
||||
// components: {
|
||||
// NButton,
|
||||
// FormInput
|
||||
// },
|
||||
|
||||
// setup() {
|
||||
// // const form = useForm({
|
||||
// // email: '',
|
||||
// // password: ''
|
||||
// // });
|
||||
// const form = reactive({
|
||||
// email: null,
|
||||
// password: null,
|
||||
// });
|
||||
|
||||
// const submit = async () => {
|
||||
// await Inertia.post('/app/register', form);
|
||||
// };
|
||||
|
||||
// return { form, submit };
|
||||
// },
|
||||
// })
|
||||
|
||||
// @Component({
|
||||
// options: {
|
||||
// layout: AuthLayout
|
||||
// },
|
||||
// name: "RegisterViewComponent",
|
||||
// components: {
|
||||
// NButton,
|
||||
// FormInput
|
||||
// },
|
||||
// })
|
||||
// export default class RegisterViewComponent extends Vue {
|
||||
|
||||
// @Prop()
|
||||
// type!: string;
|
||||
|
||||
// public form = {
|
||||
// email: null,
|
||||
// password: null,
|
||||
// };
|
||||
|
||||
// results: Array<any> = [];
|
||||
|
||||
// submit = async () => {
|
||||
// await Inertia.post('/app/register', this.form);
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// </script>
|
||||
|
||||
<!-- <style scoped>
|
||||
.greeting {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style> -->
|
14
resources/js/Stores/layout.js
Normal file
14
resources/js/Stores/layout.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export const LayoutService = defineStore('layout', {
|
||||
state: () => ({
|
||||
isAsideMobileExpanded: false, // via action
|
||||
isAsideLgActive: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
asideMobileToggle() {
|
||||
this.isAsideMobileExpanded = !this.isAsideMobileExpanded;
|
||||
},
|
||||
},
|
||||
});
|
62
resources/js/Stores/main.js
Normal file
62
resources/js/Stores/main.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import axios from 'axios';
|
||||
|
||||
export const MainService = defineStore('main', {
|
||||
state: () => ({
|
||||
/* User */
|
||||
userName: '',
|
||||
userEmail: null,
|
||||
userAvatar: null,
|
||||
|
||||
/* Field focus with ctrl+k (to register only once) */
|
||||
isFieldFocusRegistered: false,
|
||||
|
||||
/* Sample data for starting dashboard(commonly used) */
|
||||
clients: [],
|
||||
history: [],
|
||||
authors: [],
|
||||
datasets: []
|
||||
}),
|
||||
actions: {
|
||||
// payload = authenticated user
|
||||
setUser(payload) {
|
||||
if (payload.name) {
|
||||
this.userName = payload.name;
|
||||
}
|
||||
if (payload.email) {
|
||||
this.userEmail = payload.email;
|
||||
}
|
||||
if (payload.avatar) {
|
||||
this.userAvatar = payload.avatar;
|
||||
}
|
||||
},
|
||||
|
||||
fetch(sampleDataKey) {
|
||||
// sampleDataKey= clients or history
|
||||
axios
|
||||
.get(`data-sources/${sampleDataKey}.json`)
|
||||
.then((r) => {
|
||||
if (r.data && r.data.data) {
|
||||
this[sampleDataKey] = r.data.data;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error.message);
|
||||
});
|
||||
},
|
||||
|
||||
fetchApi(sampleDataKey) {
|
||||
// sampleDataKey= clients or history
|
||||
axios
|
||||
.get(`api/${sampleDataKey}`)
|
||||
.then((r) => {
|
||||
if (r.data) {
|
||||
this[sampleDataKey] = r.data;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error.message);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
57
resources/js/Stores/style.js
Normal file
57
resources/js/Stores/style.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import * as styles from '@/styles';
|
||||
import { darkModeKey, styleKey } from '@/config';
|
||||
|
||||
export const StyleService = defineStore('style', {
|
||||
state: () => ({
|
||||
/* Styles */
|
||||
asideStyle: '',
|
||||
asideScrollbarsStyle: '',
|
||||
asideBrandStyle: '',
|
||||
asideMenuItemStyle: '',
|
||||
asideMenuItemActiveStyle: '',
|
||||
asideMenuDropdownStyle: '',
|
||||
navBarItemLabelStyle: '',
|
||||
navBarItemLabelHoverStyle: '',
|
||||
navBarItemLabelActiveColorStyle: '',
|
||||
overlayStyle: '',
|
||||
|
||||
/* Dark mode default false */
|
||||
darkMode: false,
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// style payload = 'basic' or 'white' with blue font
|
||||
setStyle(payload) {
|
||||
if (!styles[payload]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(styleKey, payload);
|
||||
}
|
||||
|
||||
const style = styles[payload];
|
||||
|
||||
for (const key in style) {
|
||||
this[`${key}Style`] = style[key];
|
||||
}
|
||||
},
|
||||
// toggle dark mode
|
||||
setDarkMode(payload = null) {
|
||||
this.darkMode = payload !== null ? payload : !this.darkMode;
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(darkModeKey, this.darkMode ? '1' : '0');
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.body.classList[this.darkMode ? 'add' : 'remove']('dark-scrollbars');
|
||||
|
||||
document.documentElement.classList[this.darkMode ? 'add' : 'remove'](
|
||||
'dark-scrollbars-compat'
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
75
resources/js/app.js
Normal file
75
resources/js/app.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
import '../css/app.css';
|
||||
import { createApp, h } from 'vue';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
|
||||
import { createInertiaApp, Link, usePage } from '@inertiajs/vue3';
|
||||
import DefaultLayout from '@/Layouts/Default.vue';
|
||||
|
||||
import { createPinia } from 'pinia';
|
||||
import { StyleService } from '@/Stores/style.js';
|
||||
import { LayoutService } from '@/Stores/layout.js';
|
||||
import { MainService } from '@/Stores/main';
|
||||
import { darkModeKey, styleKey } from '@/config';
|
||||
const pinia = createPinia();
|
||||
|
||||
import { initRoutes } from '@eidellev/adonis-stardust/client';
|
||||
initRoutes();
|
||||
|
||||
// import { defineProps } from 'vue';
|
||||
|
||||
// const props = defineProps({
|
||||
// user: {
|
||||
// type: Object,
|
||||
// default: () => ({}),
|
||||
// },
|
||||
// });
|
||||
|
||||
createInertiaApp({
|
||||
progress: {
|
||||
color: '#4B5563',
|
||||
},
|
||||
// resolve: (name) => {
|
||||
// const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
|
||||
// return pages[`./Pages/${name}.vue`]
|
||||
// },
|
||||
// Webpack
|
||||
// resolve: (name) => require(`./Pages/${name}`),
|
||||
// resolve: (name) => require(`./Pages/${name}.vue`),
|
||||
// add default layout
|
||||
resolve: (name) => {
|
||||
const page = require(`./Pages/${name}.vue`).default;
|
||||
// if (!page.layout) {
|
||||
// page.layout = DefaultLayout;
|
||||
// }
|
||||
return page;
|
||||
},
|
||||
setup({ el, App, props, plugin }) {
|
||||
createApp({ render: () => h(App, props) })
|
||||
.use(plugin)
|
||||
.use(pinia)
|
||||
// .component('inertia-link', Link)
|
||||
.mount(el);
|
||||
},
|
||||
});
|
||||
|
||||
const styleService = StyleService(pinia);
|
||||
const layoutService = LayoutService(pinia);
|
||||
// const mainService = MainService(pinia);
|
||||
// mainService.setUser(user);
|
||||
|
||||
/* App style */
|
||||
styleService.setStyle(localStorage[styleKey] ?? 'basic');
|
||||
|
||||
/* Dark mode */
|
||||
if (
|
||||
(!localStorage[darkModeKey] && window.matchMedia('(prefers-color-scheme: dark)').matches) ||
|
||||
localStorage[darkModeKey] === '1'
|
||||
) {
|
||||
styleService.setDarkMode(true);
|
||||
}
|
||||
|
||||
/* Collapse mobile aside menu on route change */
|
||||
Inertia.on('navigate', (event) => {
|
||||
layoutService.isAsideMobileExpanded = false;
|
||||
layoutService.isAsideLgActive = false;
|
||||
});
|
104
resources/js/colors.js
Normal file
104
resources/js/colors.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
const gradientBgBase = 'bg-gradient-to-tr';
|
||||
export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`;
|
||||
export const gradientBgDark = `${gradientBgBase} from-slate-700 via-slate-900 to-slate-800`;
|
||||
export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`;
|
||||
export const gradientBgGreenBlue = `${gradientBgBase} from-green-400 to-blue-400`;
|
||||
|
||||
export const colorsBgLight = {
|
||||
white: 'bg-white text-black',
|
||||
light: 'bg-white text-black dark:bg-slate-900/70 dark:text-white',
|
||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||
success: 'bg-emerald-500 border-emerald-500 text-white',
|
||||
danger: 'bg-red-500 border-red-500 text-white',
|
||||
warning: 'bg-yellow-500 border-yellow-500 text-white',
|
||||
info: 'bg-blue-500 border-blue-500 text-white',
|
||||
};
|
||||
|
||||
export const colorsText = {
|
||||
white: 'text-black dark:text-slate-100',
|
||||
light: 'text-gray-700 dark:text-slate-400',
|
||||
contrast: 'dark:text-white',
|
||||
success: 'text-emerald-500',
|
||||
danger: 'text-red-500',
|
||||
warning: 'text-yellow-500',
|
||||
info: 'text-blue-500',
|
||||
};
|
||||
|
||||
export const colorsOutline = {
|
||||
white: [colorsText.white, 'border-gray-100'],
|
||||
light: [colorsText.light, 'border-gray-100'],
|
||||
contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'],
|
||||
success: [colorsText.success, 'border-emerald-500'],
|
||||
danger: [colorsText.danger, 'border-red-500'],
|
||||
warning: [colorsText.warning, 'border-yellow-500'],
|
||||
info: [colorsText.info, 'border-blue-500'],
|
||||
};
|
||||
|
||||
export const getButtonColor = (color, isOutlined, hasHover) => {
|
||||
const colors = {
|
||||
bg: {
|
||||
white: 'bg-white text-black',
|
||||
contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black',
|
||||
light: 'bg-gray-50 text-black',
|
||||
success: 'bg-emerald-600 dark:bg-emerald-500 text-white',
|
||||
danger: 'bg-red-600 dark:bg-red-500 text-white',
|
||||
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
|
||||
info: 'bg-blue-600 dark:bg-blue-500 text-white',
|
||||
},
|
||||
bgHover: {
|
||||
white: 'hover:bg-gray-50',
|
||||
contrast: 'hover:bg-gray-900 hover:dark:bg-slate-100',
|
||||
light: 'hover:bg-gray-200',
|
||||
success:
|
||||
'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-emerald-600 hover:dark:border-emerald-600',
|
||||
danger:
|
||||
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
|
||||
warning:
|
||||
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
|
||||
info: 'hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-blue-600 hover:dark:border-blue-600',
|
||||
},
|
||||
borders: {
|
||||
white: 'border-gray-100',
|
||||
contrast: 'border-gray-900 dark:border-slate-100',
|
||||
light: 'border-gray-100 dark:border-slate-700',
|
||||
success: 'border-emerald-600 dark:border-emerald-500',
|
||||
danger: 'border-red-600 dark:border-red-500',
|
||||
warning: 'border-yellow-600 dark:border-yellow-500',
|
||||
info: 'border-blue-600 dark:border-blue-500',
|
||||
},
|
||||
text: {
|
||||
white: 'text-black dark:text-slate-100',
|
||||
contrast: 'dark:text-slate-100',
|
||||
light: 'text-gray-700 dark:text-slate-400',
|
||||
success: 'text-emerald-600 dark:text-emerald-500',
|
||||
danger: 'text-red-600 dark:text-red-500',
|
||||
warning: 'text-yellow-600 dark:text-yellow-500',
|
||||
info: 'text-blue-600 dark:text-blue-500',
|
||||
},
|
||||
outlineHover: {
|
||||
white: 'hover:bg-gray-100 hover:text-gray-900 dark:hover:text-slate-900',
|
||||
contrast:
|
||||
'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black',
|
||||
light: 'hover:bg-gray-100 hover:text-gray-900 dark:hover:text-slate-900',
|
||||
success:
|
||||
'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-emerald-600',
|
||||
danger:
|
||||
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
|
||||
warning:
|
||||
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
|
||||
info: 'hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-blue-600',
|
||||
},
|
||||
};
|
||||
|
||||
if (!colors.bg[color]) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const base = [isOutlined ? colors.text[color] : colors.bg[color], colors.borders[color]];
|
||||
|
||||
if (hasHover) {
|
||||
base.push(isOutlined ? colors.outlineHover[color] : colors.bgHover[color]);
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
editor.link_modal.header
Reference in a new issue