first commit

This commit is contained in:
2024-04-03 10:16:39 +05:45
commit 3e3eac1db4
545 changed files with 841810 additions and 0 deletions

View 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>

View File

@ -0,0 +1,34 @@
<script setup>
import { computed } from 'vue';
const emit = defineEmits(['update:checked']);
const props = defineProps({
checked: {
type: [Array, Boolean],
required: true,
},
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:ring-indigo-500"
/>
</template>

View File

@ -0,0 +1,7 @@
<template>
<button
class="inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150"
>
<slot />
</button>
</template>

View File

@ -0,0 +1,77 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue';
const props = defineProps({
align: {
type: String,
default: 'right',
},
width: {
type: String,
default: '48',
},
contentClasses: {
type: String,
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 'ltr:origin-top-left rtl:origin-top-right start-0';
} else if (props.align === 'right') {
return 'ltr:origin-top-right rtl:origin-top-left end-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="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="opacity-100 scale-100"
leave-to-class="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>

View File

@ -0,0 +1,19 @@
<script setup>
import { Link } from '@inertiajs/vue3';
defineProps({
href: {
type: String,
required: true,
},
});
</script>
<template>
<Link
:href="href"
class="block w-full px-4 py-2 text-start 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>

View File

@ -0,0 +1,15 @@
<script setup>
defineProps({
message: {
type: String,
},
});
</script>
<template>
<div v-show="message">
<p class="text-sm text-red-600">
{{ message }}
</p>
</div>
</template>

View File

@ -0,0 +1,14 @@
<script setup>
defineProps({
value: {
type: String,
},
});
</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>

View File

@ -0,0 +1,98 @@
<script setup>
import { computed, onMounted, onUnmounted, watch } from 'vue';
const props = defineProps({
show: {
type: Boolean,
default: false,
},
maxWidth: {
type: String,
default: '2xl',
},
closeable: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['close']);
watch(
() => props.show,
() => {
if (props.show) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = null;
}
}
);
const close = () => {
if (props.closeable) {
emit('close');
}
};
const closeOnEscape = (e) => {
if (e.key === 'Escape' && props.show) {
close();
}
};
onMounted(() => document.addEventListener('keydown', closeOnEscape));
onUnmounted(() => {
document.removeEventListener('keydown', closeOnEscape);
document.body.style.overflow = null;
});
const maxWidthClass = computed(() => {
return {
sm: 'sm:max-w-sm',
md: 'sm:max-w-md',
lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl',
}[props.maxWidth];
});
</script>
<template>
<Teleport to="body">
<Transition leave-active-class="duration-200">
<div v-show="show" class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50" scroll-region>
<Transition
enter-active-class="ease-out duration-300"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="ease-in duration-200"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-show="show" class="fixed inset-0 transform transition-all" @click="close">
<div class="absolute inset-0 bg-gray-500 opacity-75" />
</div>
</Transition>
<Transition
enter-active-class="ease-out duration-300"
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
leave-active-class="ease-in duration-200"
leave-from-class="opacity-100 translate-y-0 sm:scale-100"
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div
v-show="show"
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full sm:mx-auto"
:class="maxWidthClass"
>
<slot v-if="show" />
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>

View File

@ -0,0 +1,26 @@
<script setup>
import { computed } from 'vue';
import { Link } from '@inertiajs/vue3';
const props = defineProps({
href: {
type: String,
required: true,
},
active: {
type: Boolean,
},
});
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>

View File

@ -0,0 +1,7 @@
<template>
<button
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 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150"
>
<slot />
</button>
</template>

View File

@ -0,0 +1,26 @@
<script setup>
import { computed } from 'vue';
import { Link } from '@inertiajs/vue3';
const props = defineProps({
href: {
type: String,
required: true,
},
active: {
type: Boolean,
},
});
const classes = computed(() =>
props.active
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start 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 w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start 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>

View File

@ -0,0 +1,17 @@
<script setup>
defineProps({
type: {
type: String,
default: 'button',
},
});
</script>
<template>
<button
:type="type"
class="inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150"
>
<slot />
</button>
</template>

View File

@ -0,0 +1,26 @@
<script setup>
import { onMounted, ref } from 'vue';
const model = defineModel({
type: String,
required: true,
});
const input = ref(null);
onMounted(() => {
if (input.value.hasAttribute('autofocus')) {
input.value.focus();
}
});
defineExpose({ focus: () => input.value.focus() });
</script>
<template>
<input
class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
v-model="model"
ref="input"
/>
</template>

View File

@ -0,0 +1,408 @@
<script setup>
import { ref, computed } from 'vue';
import { usePrimeVue } from 'primevue/config';
import { useLayout } from '@/Layouts/composables/layout';
defineProps({
simple: {
type: Boolean,
default: false
}
});
const $primevue = usePrimeVue();
const inputStyle = computed(() => $primevue.config.inputStyle || 'outlined');
const scales = ref([12, 13, 14, 15, 16]);
const visible = ref(false);
const inputStyles = ref([
{ label: 'Outlined', value: 'outlined' },
{ label: 'Filled', value: 'filled' }
]);
const menuModes = ref([
{ label: 'Static', value: 'static' },
{ label: 'Overlay', value: 'overlay' }
]);
const compactMaterial = ref(false);
const primaryFocusRing = ref(true);
const { setScale, layoutConfig } = useLayout();
const onConfigButtonClick = () => {
visible.value = !visible.value;
};
const onChangeTheme = (theme, mode) => {
$primevue.changeTheme(layoutConfig.theme.value, theme, 'theme-css', () => {
layoutConfig.theme.value = theme;
layoutConfig.darkTheme.value = mode;
});
};
const decrementScale = () => {
setScale(layoutConfig.scale.value - 1);
applyScale();
};
const incrementScale = () => {
setScale(layoutConfig.scale.value + 1);
applyScale();
};
const applyScale = () => {
document.documentElement.style.fontSize = layoutConfig.scale.value + 'px';
};
const onInputStyleChange = (value) => {
$primevue.config.inputStyle = value;
};
const onMenuModeChange = (value) => {
layoutConfig.menuMode.value = value;
};
const onRippleChange = (value) => {
layoutConfig.ripple.value = value;
};
const onDarkModeChange = (value) => {
const newThemeName = value ? layoutConfig.theme.value.replace('light', 'dark') : layoutConfig.theme.value.replace('dark', 'light');
layoutConfig.darkTheme.value = value;
onChangeTheme(newThemeName, value);
};
const changeTheme = (theme, color) => {
let newTheme, dark;
newTheme = theme + '-' + (layoutConfig.darkTheme.value ? 'dark' : 'light');
if (color) {
newTheme += '-' + color;
}
if (newTheme.startsWith('md-') && compactMaterial.value) {
newTheme = newTheme.replace('md-', 'mdc-');
}
dark = layoutConfig.darkTheme.value;
onChangeTheme(newTheme, dark);
};
const isThemeActive = (themeFamily, color) => {
let themeName;
let themePrefix = themeFamily === 'md' && compactMaterial.value ? 'mdc' : themeFamily;
themeName = themePrefix + (layoutConfig.darkTheme.value ? '-dark' : '-light');
if (color) {
themeName += '-' + color;
}
return layoutConfig.theme.value === themeName;
};
const onCompactMaterialChange = (value) => {
compactMaterial.value = value;
if (layoutConfig.theme.value.startsWith('md')) {
let tokens = layoutConfig.theme.value.split('-');
changeTheme(tokens[0].substring(0, 2), tokens[2]);
}
};
const onFocusRingColorChange = (value) => {
primaryFocusRing.value = value;
let root = document.documentElement;
if (value) {
if (layoutConfig.darkTheme.value) root.style.setProperty('--p-focus-ring-color', 'var(--primary-500)');
else root.style.setProperty('--p-focus-ring-color', 'var(--primary-500)');
} else {
if (layoutConfig.darkTheme.value) root.style.setProperty('--p-focus-ring-color', 'var(--surface-500)');
else root.style.setProperty('--p-focus-ring-color', 'var(--surface-900)');
}
};
</script>
<template>
<button class="layout-config-button p-link" type="button" @click="onConfigButtonClick()">
<i class="pi pi-cog"></i>
</button>
<Sidebar v-model:visible="visible" position="right" class="layout-config-sidebar w-26rem" pt:closeButton="ml-auto">
<div class="p-2">
<section class="pb-4 flex align-items-center justify-content-between border-bottom-1 surface-border">
<span class="text-xl font-semibold">Scale</span>
<div class="flex align-items-center gap-2 border-1 surface-border py-1 px-2" style="border-radius: 30px">
<Button icon="pi pi-minus" @click="decrementScale" text rounded :disabled="layoutConfig.scale.value === scales[0]" />
<i v-for="s in scales" :key="s" :class="['pi pi-circle-fill text-sm text-200', { 'text-lg text-primary': s === layoutConfig.scale.value }]" />
<Button icon="pi pi-plus" @click="incrementScale" text rounded :disabled="layoutConfig.scale.value === scales[scales.length - 1]" />
</div>
</section>
<section class="py-4 flex align-items-center justify-content-between border-bottom-1 surface-border">
<span :class="['text-xl font-semibold']">Dark Mode</span>
<InputSwitch :modelValue="layoutConfig.darkTheme.value" @update:modelValue="onDarkModeChange" />
</section>
<template v-if="!simple">
<section class="py-4 flex align-items-center justify-content-between border-bottom-1 surface-border">
<span class="text-xl font-semibold">Menu Type</span>
<SelectButton :modelValue="layoutConfig.menuMode.value" @update:modelValue="onMenuModeChange" :options="menuModes" optionLabel="label" optionValue="value" :allowEmpty="false" />
</section>
<section class="py-4 flex align-items-center justify-content-between border-bottom-1 surface-border">
<span class="text-xl font-semibold">Input Variant</span>
<SelectButton :modelValue="inputStyle" @update:modelValue="onInputStyleChange" :options="inputStyles" optionLabel="label" optionValue="value" :allowEmpty="false" />
</section>
</template>
<section class="py-4 flex align-items-center justify-content-between border-bottom-1 surface-border">
<span class="text-xl font-semibold">Ripple Effect</span>
<InputSwitch :modelValue="layoutConfig.ripple.value" @update:modelValue="onRippleChange" />
</section>
<section class="py-4 border-bottom-1 surface-border">
<div class="text-xl font-semibold mb-3">Themes</div>
<div class="flex align-items-center gap-2 mb-3">
<img src="https://primefaces.org/cdn/primevue/images/themes/aura.png" alt="Aura" style="width: 1.5rem" />
<span class="font-medium">Aura</span>
</div>
<div class="flex align-items-center justify-content-between gap-3 mb-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'green'), 'hover:border-500 surface-border': !isThemeActive('aura', 'green') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'green')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #4dac9c 0%, rgba(77, 172, 156, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'cyan'), 'hover:border-500 surface-border': !isThemeActive('aura', 'cyan') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'cyan')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #06b6d4 0%, rgba(6, 182, 212, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'blue'), 'hover:border-500 surface-border': !isThemeActive('aura', 'blue') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'blue')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #4378e6 0%, rgba(67, 120, 230, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'indigo'), 'hover:border-500 surface-border': !isThemeActive('aura', 'indigo') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'indigo')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #585fe0 0%, rgba(88, 95, 224, 0.5) 100%)"></span>
</button>
</div>
<div class="flex align-items-center justify-content-between gap-3 mb-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'purple'), 'hover:border-500 surface-border': !isThemeActive('aura', 'purple') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'purple')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #7758e4 0%, rgba(119, 88, 228, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'amber'), 'hover:border-500 surface-border': !isThemeActive('aura', 'amber') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'amber')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #f59e0b 0%, rgba(245, 158, 11, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'teal'), 'hover:border-500 surface-border': !isThemeActive('aura', 'teal') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'teal')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #14b8a6 0%, rgba(20, 184, 166, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'pink'), 'hover:border-500 surface-border': !isThemeActive('aura', 'pink') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'pink')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #ec4899 0%, rgba(236, 72, 153, 0.5) 100%)"></span>
</button>
</div>
<div class="flex align-items-center justify-content-between gap-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'noir'), 'hover:border-500 surface-border': !isThemeActive('aura', 'noir') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'noir')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #0f172a 0%, rgba(0, 0, 0, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('aura', 'lime'), 'hover:border-500 surface-border': !isThemeActive('aura', 'lime') }
]"
style="border-radius: 30px"
@click="changeTheme('aura', 'lime')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #84cc16 0%, rgb(132, 204, 22, 0.5) 100%)"></span>
</button>
<span class="w-3"></span>
<span class="w-3"></span>
</div>
<section class="pt-4 flex align-items-center justify-content-between">
<span class="text-sm">Primary Focus Ring</span>
<InputSwitch :modelValue="primaryFocusRing" @update:modelValue="onFocusRingColorChange" />
</section>
</section>
<section class="py-4 border-bottom-1 surface-border">
<div class="flex align-items-center gap-2 mb-3">
<img src="https://primefaces.org/cdn/primevue/images/themes/lara-light-teal.png" alt="Lara Light Teal" class="border-circle" style="width: 1.5rem" />
<span class="font-medium">Lara</span>
</div>
<div class="flex align-items-center justify-content-between gap-3 mb-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'green'), 'hover:border-500 surface-border': !isThemeActive('lara', 'green') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'green')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #4dac9c 0%, rgba(77, 172, 156, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'cyan'), 'hover:border-500 surface-border': !isThemeActive('lara', 'cyan') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'cyan')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #06b6d4 0%, rgba(6, 182, 212, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'blue'), 'hover:border-500 surface-border': !isThemeActive('lara', 'blue') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'blue')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #4378e6 0%, rgba(67, 120, 230, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'indigo'), 'hover:border-500 surface-border': !isThemeActive('lara', 'indigo') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'indigo')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #585fe0 0%, rgba(88, 95, 224, 0.5) 100%)"></span>
</button>
</div>
<div class="flex align-items-center justify-content-between gap-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'purple'), 'hover:border-500 surface-border': !isThemeActive('lara', 'purple') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'purple')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #7758e4 0%, rgba(119, 88, 228, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'amber'), 'hover:border-500 surface-border': !isThemeActive('lara', 'amber') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'amber')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #f59e0b 0%, rgba(245, 158, 11, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'teal'), 'hover:border-500 surface-border': !isThemeActive('lara', 'teal') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'teal')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #14b8a6 0%, rgba(20, 184, 166, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('lara', 'pink'), 'hover:border-500 surface-border': !isThemeActive('lara', 'pink') }
]"
style="border-radius: 30px"
@click="changeTheme('lara', 'pink')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #ec4899 0%, rgba(236, 72, 153, 0.5) 100%)"></span>
</button>
</div>
</section>
<section class="py-4 border-bottom-1 surface-border">
<div class="flex align-items-center gap-2 mb-3">
<img src="https://primefaces.org/cdn/primevue/images/themes/md-light-indigo.svg" alt="Material Design" class="border-circle" style="width: 1.5rem" />
<span class="font-medium">Material Design</span>
<div class="ml-auto flex align-items-center gap-2">
<label for="material-condensed" class="text-sm">Condensed</label>
<InputSwitch inputId="material-condensed" :modelValue="compactMaterial" @update:modelValue="onCompactMaterialChange" class="ml-auto" />
</div>
</div>
<div class="flex align-items-center justify-content-between gap-3">
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('md', 'indigo'), 'hover:border-500 surface-border': !isThemeActive('md', 'indigo') }
]"
style="border-radius: 30px"
@click="changeTheme('md', 'indigo')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #0565f2 0%, rgba(5, 101, 242, 0.5) 100%)"></span>
</button>
<button
:class="[
'bg-transparent border-1 cursor-pointer p-2 w-3 flex align-items-center justify-content-center transition-all transition-duration-200',
{ 'border-primary': isThemeActive('md', 'deeppurple'), 'hover:border-500 surface-border': !isThemeActive('md', 'deeppurple') }
]"
style="border-radius: 30px"
@click="changeTheme('md', 'deeppurple')"
>
<span class="block h-1rem w-full" style="border-radius: 30px; background: linear-gradient(180deg, #702f92 0%, rgba(112, 47, 146, 0.5) 100%)"></span>
</button>
<div class="w-3"></div>
<div class="w-3"></div>
</div>
</section>
</div>
</Sidebar>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,20 @@
<script setup>
import { useLayout } from '@/Layouts/composables/layout';
import { computed } from 'vue';
const { layoutConfig } = useLayout();
const logoUrl = computed(() => {
return `/layout/images/${layoutConfig.darkTheme.value ? 'logo-white' : 'logo-dark'}.svg`;
});
</script>
<template>
<div class="layout-footer">
<img :src="logoUrl" alt="Logo" height="20" class="mr-2" />
by
<span class="font-medium ml-2">PrimeVue</span>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,78 @@
<script setup>
import { computed, watch, ref } from 'vue';
import AppTopbar from './AppTopbar.vue';
import AppFooter from './AppFooter.vue';
import AppSidebar from './AppSidebar.vue';
import AppConfig from './AppConfig.vue';
import { useLayout } from '@/Layouts/composables/layout';
import Toast from 'primevue/toast';
const { layoutConfig, layoutState, isSidebarActive } = useLayout();
const outsideClickListener = ref(null);
watch(isSidebarActive, (newVal) => {
if (newVal) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const containerClass = computed(() => {
return {
'layout-theme-light': layoutConfig.darkTheme.value === 'light',
'layout-theme-dark': layoutConfig.darkTheme.value === 'dark',
'layout-overlay': layoutConfig.menuMode.value === 'overlay',
'layout-static': layoutConfig.menuMode.value === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive.value && layoutConfig.menuMode.value === 'static',
'layout-overlay-active': layoutState.overlayMenuActive.value,
'layout-mobile-active': layoutState.staticMenuMobileActive.value,
'p-ripple-disabled': layoutConfig.ripple.value === false
};
});
const bindOutsideClickListener = () => {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
layoutState.overlayMenuActive.value = false;
layoutState.staticMenuMobileActive.value = false;
layoutState.menuHoverActive.value = false;
}
};
document.addEventListener('click', outsideClickListener.value);
}
};
const unbindOutsideClickListener = () => {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
};
const isOutsideClicked = (event) => {
const sidebarEl = document.querySelector('.layout-sidebar');
const topbarEl = document.querySelector('.layout-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
};
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<div class="layout-sidebar">
<app-sidebar></app-sidebar>
</div>
<div class="layout-main-container">
<div class="layout-main">
<main><slot/></main>
</div>
<app-footer></app-footer>
</div>
<!-- <app-config></app-config> -->
<div class="layout-mask"></div>
</div>
<Toast />
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,25 @@
<script setup>
import { ref } from 'vue';
import AppMenuItem from './AppMenuItem.vue';
const model = ref([
{
items: [
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/' },
{ label: 'User', icon: 'pi pi-fw pi-user', to: '/user' }]
},
]);
</script>
<template>
<ul class="layout-menu">
<template v-for="(item, i) in model" :key="item">
<app-menu-item v-if="!item.separator" :item="item" :index="i"></app-menu-item>
<li v-if="item.separator" class="menu-separator"></li>
</template>
</ul>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,117 @@
<script setup>
import { ref, onBeforeMount, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useLayout } from '@/Layouts/composables/layout';
import { Head, Link, useForm } from '@inertiajs/vue3';
// const route = useRoute();
const { layoutConfig, layoutState, setActiveMenuItem, onMenuToggle } = useLayout();
const props = defineProps({
item: {
type: Object,
default: () => ({})
},
index: {
type: Number,
default: 0
},
root: {
type: Boolean,
default: true
},
parentItemKey: {
type: String,
default: null
}
});
const isActiveMenu = ref(false);
const itemKey = ref(null);
onBeforeMount(() => {
itemKey.value = props.parentItemKey ? props.parentItemKey + '-' + props.index : String(props.index);
const activeItem = layoutState.activeMenuItem;
isActiveMenu.value = activeItem === itemKey.value || activeItem ? activeItem.startsWith(itemKey.value + '-') : false;
});
watch(
() => layoutConfig.activeMenuItem.value,
(newVal) => {
isActiveMenu.value = newVal === itemKey.value || newVal.startsWith(itemKey.value + '-');
}
);
const itemClick = (event, item) => {
if (item.disabled) {
event.preventDefault();
return;
}
const { overlayMenuActive, staticMenuMobileActive } = layoutState;
if ((item.to || item.url) && (staticMenuMobileActive.value || overlayMenuActive.value)) {
onMenuToggle();
}
if (item.command) {
item.command({ originalEvent: event, item: item });
}
const foundItemKey = item.items ? (isActiveMenu.value ? props.parentItemKey : itemKey) : itemKey.value;
setActiveMenuItem(foundItemKey);
};
const checkActiveRoute = (item) => {
console.log(route);
return route.path === item.to;
};
</script>
<template>
<li :class="{ 'layout-root-menuitem': root, 'active-menuitem': isActiveMenu }">
<div v-if="root && item.visible !== false" class="layout-menuitem-root-text">{{ item.label }}</div>
<a v-if="(!item.to || item.items) && item.visible !== false" :href="item.url" @click="itemClick($event, item, index)" :class="item.class" :target="item.target" tabindex="0">
<i :class="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{ item.label }}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items"></i>
</a>
<Link v-if="item.to && !item.items && item.visible !== false" @click="itemClick($event, item, index)" :class="[item.class, { 'active-route': checkActiveRoute(item) }]" tabindex="0" :to="item.to">
<i :class="item.icon" class="layout-menuitem-icon"></i>
<span class="layout-menuitem-text">{{ item.label }}</span>
<i class="pi pi-fw pi-angle-down layout-submenu-toggler" v-if="item.items"></i>
</Link>
<Transition v-if="item.items && item.visible !== false" name="layout-submenu">
<ul v-show="root ? true : isActiveMenu" class="layout-submenu">
<app-menu-item v-for="(child, i) in item.items" :key="child" :index="i" :item="child" :parentItemKey="itemKey" :root="false"></app-menu-item>
</ul>
</Transition>
</li>
<!-- <ul class="layout-menu">
<li class="layout-root-menuitem">
<div class="layout-menuitem-root-text"></div>
<ul class="layout-submenu">
<li class="">
<Link :href="route('dashboard')" class="" tabindex="0">
<i class="pi pi-fw pi-home layout-menuitem-icon"></i>
<span class="layout-menuitem-text">Dashboard</span>
</Link>
</li>
<li class="">
<Link :href="route('user.index')" class="" tabindex="0">
<i class="pi pi-fw pi-user layout-menuitem-icon"></i>
<span class="layout-menuitem-text">User</span></Link>
</li>
</ul>
</li>
</ul> -->
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,9 @@
<script setup>
import AppMenu from './AppMenu.vue';
</script>
<template>
<app-menu></app-menu>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,97 @@
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useLayout } from '@/Layouts/composables/layout';
import { useRouter } from 'vue-router';
const { layoutConfig, onMenuToggle } = useLayout();
const outsideClickListener = ref(null);
const topbarMenuActive = ref(false);
const router = useRouter();
onMounted(() => {
bindOutsideClickListener();
});
onBeforeUnmount(() => {
unbindOutsideClickListener();
});
const logoUrl = computed(() => {
return `/layout/images/${layoutConfig.darkTheme.value ? 'logo-white' : 'logo-dark'}.svg`;
});
const onTopBarMenuButton = () => {
topbarMenuActive.value = !topbarMenuActive.value;
};
const onSettingsClick = () => {
topbarMenuActive.value = false;
router.push('/documentation');
};
const topbarMenuClasses = computed(() => {
return {
'layout-topbar-menu-mobile-active': topbarMenuActive.value
};
});
const bindOutsideClickListener = () => {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
topbarMenuActive.value = false;
}
};
document.addEventListener('click', outsideClickListener.value);
}
};
const unbindOutsideClickListener = () => {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
};
const isOutsideClicked = (event) => {
if (!topbarMenuActive.value) return;
const sidebarEl = document.querySelector('.layout-topbar-menu');
const topbarEl = document.querySelector('.layout-topbar-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
};
</script>
<template>
<div class="layout-topbar">
<router-link to="/" class="layout-topbar-logo">
<img :src="logoUrl" alt="logo" />
<span>SAKAI</span>
</router-link>
<button class="p-link layout-menu-button layout-topbar-button" @click="onMenuToggle()">
<i class="pi pi-bars"></i>
</button>
<button class="p-link layout-topbar-menu-button layout-topbar-button" @click="onTopBarMenuButton()">
<i class="pi pi-ellipsis-v"></i>
</button>
<div class="layout-topbar-menu" :class="topbarMenuClasses">
<button @click="onTopBarMenuButton()" class="p-link layout-topbar-button">
<i class="pi pi-calendar"></i>
<span>Calendar</span>
</button>
<button @click="onTopBarMenuButton()" class="p-link layout-topbar-button">
<i class="pi pi-user"></i>
<span>Profile</span>
</button>
<button @click="onSettingsClick()" class="p-link layout-topbar-button">
<i class="pi pi-cog"></i>
<span>Settings</span>
</button>
</div>
</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,78 @@
<script setup>
import { computed, watch, ref } from 'vue';
import AppTopbar from './AppTopbar.vue';
import AppFooter from './AppFooter.vue';
import AppSidebar from './AppSidebar.vue';
import AppConfig from './AppConfig.vue';
import { useLayout } from '@/Layouts/composables/layout';
import Toast from 'primevue/toast';
const { layoutConfig, layoutState, isSidebarActive } = useLayout();
const outsideClickListener = ref(null);
watch(isSidebarActive, (newVal) => {
if (newVal) {
bindOutsideClickListener();
} else {
unbindOutsideClickListener();
}
});
const containerClass = computed(() => {
return {
'layout-theme-light': layoutConfig.darkTheme.value === 'light',
'layout-theme-dark': layoutConfig.darkTheme.value === 'dark',
'layout-overlay': layoutConfig.menuMode.value === 'overlay',
'layout-static': layoutConfig.menuMode.value === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive.value && layoutConfig.menuMode.value === 'static',
'layout-overlay-active': layoutState.overlayMenuActive.value,
'layout-mobile-active': layoutState.staticMenuMobileActive.value,
'p-ripple-disabled': layoutConfig.ripple.value === false
};
});
const bindOutsideClickListener = () => {
if (!outsideClickListener.value) {
outsideClickListener.value = (event) => {
if (isOutsideClicked(event)) {
layoutState.overlayMenuActive.value = false;
layoutState.staticMenuMobileActive.value = false;
layoutState.menuHoverActive.value = false;
}
};
document.addEventListener('click', outsideClickListener.value);
}
};
const unbindOutsideClickListener = () => {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener);
outsideClickListener.value = null;
}
};
const isOutsideClicked = (event) => {
const sidebarEl = document.querySelector('.layout-sidebar');
const topbarEl = document.querySelector('.layout-menu-button');
return !(sidebarEl.isSameNode(event.target) || sidebarEl.contains(event.target) || topbarEl.isSameNode(event.target) || topbarEl.contains(event.target));
};
</script>
<template>
<div class="layout-wrapper" :class="containerClass">
<app-topbar></app-topbar>
<div class="layout-sidebar">
<app-sidebar></app-sidebar>
</div>
<div class="layout-main-container">
<div class="layout-main">
<main><slot/></main>
</div>
<app-footer></app-footer>
</div>
<!-- <app-config></app-config> -->
<div class="layout-mask"></div>
</div>
<Toast />
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,20 @@
<script setup>
import ApplicationLogo from '@/Components/ApplicationLogo.vue';
import { Link } from '@inertiajs/vue3';
</script>
<template>
<div class="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
<div>
<Link href="/">
<ApplicationLogo class="w-20 h-20 fill-current text-gray-500" />
</Link>
</div>
<div
class="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg"
>
<slot />
</div>
</div>
</template>

View File

@ -0,0 +1,48 @@
import { toRefs, reactive, computed } from 'vue';
const layoutConfig = reactive({
ripple: true,
darkTheme: false,
inputStyle: 'outlined',
menuMode: 'static',
theme: 'aura-light-green',
scale: 14,
activeMenuItem: null
});
const layoutState = reactive({
staticMenuDesktopInactive: false,
overlayMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
staticMenuMobileActive: false,
menuHoverActive: false
});
export function useLayout() {
const setScale = (scale) => {
layoutConfig.scale = scale;
};
const setActiveMenuItem = (item) => {
layoutConfig.activeMenuItem = item.value || item;
};
const onMenuToggle = () => {
if (layoutConfig.menuMode === 'overlay') {
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
}
if (window.innerWidth > 991) {
layoutState.staticMenuDesktopInactive = !layoutState.staticMenuDesktopInactive;
} else {
layoutState.staticMenuMobileActive = !layoutState.staticMenuMobileActive;
}
};
const isSidebarActive = computed(() => layoutState.overlayMenuActive || layoutState.staticMenuMobileActive);
const isDarkTheme = computed(() => layoutConfig.darkTheme);
return { layoutConfig: toRefs(layoutConfig), layoutState: toRefs(layoutState), setScale, onMenuToggle, isSidebarActive, isDarkTheme, setActiveMenuItem };
}

View File

@ -0,0 +1,50 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, useForm } from '@inertiajs/vue3';
const form = useForm({
password: '',
});
const submit = () => {
form.post(route('password.confirm'), {
onFinish: () => form.reset(),
});
};
</script>
<template>
<GuestLayout>
<Head title="Confirm Password" />
<div class="mb-4 text-sm text-gray-600">
This is a secure area of the application. Please confirm your password before continuing.
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="current-password"
autofocus
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="flex justify-end mt-4">
<PrimaryButton class="ms-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Confirm
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@ -0,0 +1,61 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, useForm } from '@inertiajs/vue3';
defineProps({
status: {
type: String,
},
});
const form = useForm({
email: '',
});
const submit = () => {
form.post(route('password.email'));
};
</script>
<template>
<GuestLayout>
<Head title="Forgot Password" />
<div class="mb-4 text-sm text-gray-600">
Forgot your password? No problem. Just let us know your email address and we will email you a password reset
link that will allow you to choose a new one.
</div>
<div v-if="status" class="mb-4 font-medium text-sm text-green-600">
{{ status }}
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autofocus
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div class="flex items-center justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Email Password Reset Link
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@ -0,0 +1,94 @@
<script setup>
import Checkbox from '@/Components/Checkbox.vue';
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';
defineProps({
canResetPassword: {
type: Boolean,
},
status: {
type: String,
},
});
const form = useForm({
email: '',
password: '',
remember: false,
});
const submit = () => {
form.post(route('login'), {
onFinish: () => form.reset('password'),
});
};
</script>
<template>
<GuestLayout>
<Head title="Log in" />
<div v-if="status" class="mb-4 font-medium text-sm text-green-600">
{{ status }}
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autofocus
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div class="mt-4">
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="current-password"
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="block mt-4">
<label class="flex items-center">
<Checkbox name="remember" v-model:checked="form.remember" />
<span class="ms-2 text-sm text-gray-600">Remember me</span>
</label>
</div>
<div class="flex items-center justify-end mt-4">
<Link
v-if="canResetPassword"
:href="route('password.request')"
class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Forgot your password?
</Link>
<PrimaryButton class="ms-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Log in
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@ -0,0 +1,103 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';
const form = useForm({
name: '',
email: '',
password: '',
password_confirmation: '',
});
const submit = () => {
form.post(route('register'), {
onFinish: () => form.reset('password', 'password_confirmation'),
});
};
</script>
<template>
<GuestLayout>
<Head title="Register" />
<form @submit.prevent="submit">
<div>
<InputLabel for="name" value="Name" />
<TextInput
id="name"
type="text"
class="mt-1 block w-full"
v-model="form.name"
required
autofocus
autocomplete="name"
/>
<InputError class="mt-2" :message="form.errors.name" />
</div>
<div class="mt-4">
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div class="mt-4">
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="new-password"
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="mt-4">
<InputLabel for="password_confirmation" value="Confirm Password" />
<TextInput
id="password_confirmation"
type="password"
class="mt-1 block w-full"
v-model="form.password_confirmation"
required
autocomplete="new-password"
/>
<InputError class="mt-2" :message="form.errors.password_confirmation" />
</div>
<div class="flex items-center justify-end mt-4">
<Link
:href="route('login')"
class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Already registered?
</Link>
<PrimaryButton class="ms-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Register
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@ -0,0 +1,92 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, useForm } from '@inertiajs/vue3';
const props = defineProps({
email: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
});
const form = useForm({
token: props.token,
email: props.email,
password: '',
password_confirmation: '',
});
const submit = () => {
form.post(route('password.store'), {
onFinish: () => form.reset('password', 'password_confirmation'),
});
};
</script>
<template>
<GuestLayout>
<Head title="Reset Password" />
<form @submit.prevent="submit">
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autofocus
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div class="mt-4">
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="new-password"
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="mt-4">
<InputLabel for="password_confirmation" value="Confirm Password" />
<TextInput
id="password_confirmation"
type="password"
class="mt-1 block w-full"
v-model="form.password_confirmation"
required
autocomplete="new-password"
/>
<InputError class="mt-2" :message="form.errors.password_confirmation" />
</div>
<div class="flex items-center justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Reset Password
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View File

@ -0,0 +1,51 @@
<script setup>
import { computed } from 'vue';
import GuestLayout from '@/Layouts/GuestLayout.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';
const props = defineProps({
status: {
type: String,
},
});
const form = useForm({});
const submit = () => {
form.post(route('verification.send'));
};
const verificationLinkSent = computed(() => props.status === 'verification-link-sent');
</script>
<template>
<GuestLayout>
<Head title="Email Verification" />
<div class="mb-4 text-sm text-gray-600">
Thanks for signing up! Before getting started, could you verify your email address by clicking on the link
we just emailed to you? If you didn't receive the email, we will gladly send you another.
</div>
<div class="mb-4 font-medium text-sm text-green-600" v-if="verificationLinkSent">
A new verification link has been sent to the email address you provided during registration.
</div>
<form @submit.prevent="submit">
<div class="mt-4 flex items-center justify-between">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Resend Verification Email
</PrimaryButton>
<Link
:href="route('logout')"
method="post"
as="button"
class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>Log Out</Link
>
</div>
</form>
</GuestLayout>
</template>

361
resources/js/Pages/Crud.vue Normal file
View File

@ -0,0 +1,361 @@
<script setup>
import { FilterMatchMode } from 'primevue/api';
import { ref, onMounted, onBeforeMount } from 'vue';
import { ProductService } from '@/service/ProductService';
import { useToast } from 'primevue/usetoast';
import Toolbar from 'primevue/toolbar';
import Button from 'primevue/button';
import FileUpload from 'primevue/fileupload';
import InputIcon from 'primevue/inputicon';
import InputText from 'primevue/inputtext';
import Column from 'primevue/column';
import Rating from 'primevue/rating';
import Tag from 'primevue/tag';
import DataTable from 'primevue/datatable';
import Textarea from 'primevue/textarea';
import Dropdown from 'primevue/dropdown';
import IconField from 'primevue/iconfield';
import RadioButton from 'primevue/radiobutton';
import Row from 'primevue/row';
import SelectButton from 'primevue/selectbutton';
import InputNumber from 'primevue/inputnumber';
import Dialog from 'primevue/dialog';
import DialogService from 'primevue/dialogservice';
import AppLayout from '@/Layouts/AppLayout.vue';
const toast = useToast();
const products = ref(null);
const productDialog = ref(false);
const deleteProductDialog = ref(false);
const deleteProductsDialog = ref(false);
const product = ref({});
const selectedProducts = ref(null);
const dt = ref(null);
const filters = ref({});
const submitted = ref(false);
const statuses = ref([
{ label: 'INSTOCK', value: 'instock' },
{ label: 'LOWSTOCK', value: 'lowstock' },
{ label: 'OUTOFSTOCK', value: 'outofstock' }
]);
const productService = new ProductService();
const getBadgeSeverity = (inventoryStatus) => {
switch (inventoryStatus.toLowerCase()) {
case 'instock':
return 'success';
case 'lowstock':
return 'warning';
case 'outofstock':
return 'danger';
default:
return 'info';
}
};
onBeforeMount(() => {
initFilters();
});
onMounted(() => {
productService.getProducts().then((data) => (products.value = data));
});
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const openNew = () => {
product.value = {};
submitted.value = false;
productDialog.value = true;
};
const hideDialog = () => {
productDialog.value = false;
submitted.value = false;
};
const saveProduct = () => {
submitted.value = true;
if (product.value.name && product.value.name.trim() && product.value.price) {
if (product.value.id) {
product.value.inventoryStatus = product.value.inventoryStatus.value ? product.value.inventoryStatus.value : product.value.inventoryStatus;
products.value[findIndexById(product.value.id)] = product.value;
toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Updated', life: 3000 });
} else {
product.value.id = createId();
product.value.code = createId();
product.value.image = 'product-placeholder.svg';
product.value.inventoryStatus = product.value.inventoryStatus ? product.value.inventoryStatus.value : 'INSTOCK';
products.value.push(product.value);
toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Created', life: 3000 });
}
productDialog.value = false;
product.value = {};
}
};
const editProduct = (editProduct) => {
product.value = { ...editProduct };
productDialog.value = true;
};
const confirmDeleteProduct = (editProduct) => {
product.value = editProduct;
deleteProductDialog.value = true;
};
const deleteProduct = () => {
products.value = products.value.filter((val) => val.id !== product.value.id);
deleteProductDialog.value = false;
product.value = {};
toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Deleted', life: 3000 });
};
const findIndexById = (id) => {
let index = -1;
for (let i = 0; i < products.value.length; i++) {
if (products.value[i].id === id) {
index = i;
break;
}
}
return index;
};
const createId = () => {
let id = '';
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 5; i++) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
};
const exportCSV = () => {
dt.value.exportCSV();
};
const confirmDeleteSelected = () => {
deleteProductsDialog.value = true;
};
const deleteSelectedProducts = () => {
products.value = products.value.filter((val) => !selectedProducts.value.includes(val));
deleteProductsDialog.value = false;
selectedProducts.value = null;
toast.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 });
};
const initFilters = () => {
filters.value = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
};
};
</script>
<template>
<app-layout>
<div class="grid">
<div class="col-12">
<div class="card">
<Toolbar class="mb-4">
<template v-slot:start>
<div class="my-2">
<Button label="New" icon="pi pi-plus" class="mr-2" severity="success"
@click="openNew" />
<Button label="Delete" icon="pi pi-trash" severity="danger"
@click="confirmDeleteSelected"
:disabled="!selectedProducts || !selectedProducts.length" />
</div>
</template>
<template v-slot:end>
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import"
chooseLabel="Import" class="mr-2 inline-block" />
<Button label="Export" icon="pi pi-upload" severity="help" @click="exportCSV($event)" />
</template>
</Toolbar>
<DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id"
:paginator="true" :rows="10" :filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[5, 10, 25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products">
<template #header>
<div class="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
<h5 class="m-0">Manage Products</h5>
<IconField iconPosition="left" class="block mt-2 md:mt-0">
<InputIcon class="pi pi-search" />
<InputText class="w-full sm:w-auto" v-model="filters['global'].value"
placeholder="Search..." />
</IconField>
</div>
</template>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code" :sortable="true" headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Code</span>
{{ slotProps.data.code }}
</template>
</Column>
<Column field="name" header="Name" :sortable="true" headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Name</span>
{{ slotProps.data.name }}
</template>
</Column>
<Column header="Image" headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Image</span>
<img :src="'/demo/images/product/' + slotProps.data.image" :alt="slotProps.data.image"
class="shadow-2" width="100" />
</template>
</Column>
<Column field="price" header="Price" :sortable="true" headerStyle="width:14%; min-width:8rem;">
<template #body="slotProps">
<span class="p-column-title">Price</span>
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" :sortable="true"
headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Category</span>
{{ slotProps.data.category }}
</template>
</Column>
<Column field="rating" header="Reviews" :sortable="true"
headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Rating</span>
<Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" :sortable="true"
headerStyle="width:14%; min-width:10rem;">
<template #body="slotProps">
<span class="p-column-title">Status</span>
<Tag :severity="getBadgeSeverity(slotProps.data.inventoryStatus)">{{
slotProps.data.inventoryStatus }}</Tag>
</template>
</Column>
<Column headerStyle="min-width:10rem;">
<template #body="slotProps">
<Button icon="pi pi-pencil" class="mr-2" severity="success" rounded
@click="editProduct(slotProps.data)" />
<Button icon="pi pi-trash" class="mt-2" severity="warning" rounded
@click="confirmDeleteProduct(slotProps.data)" />
</template>
</Column>
</DataTable>
<Dialog v-model:visible="productDialog" :style="{ width: '450px' }" header="Product Details"
:modal="true" class="p-fluid">
<img :src="'/demo/images/product/' + product.image" :alt="product.image" v-if="product.image"
width="150" class="mt-0 mx-auto mb-5 block shadow-2" />
<div class="field">
<label for="name">Name</label>
<InputText id="name" v-model.trim="product.name" required="true" autofocus
:invalid="submitted && !product.name" />
<small class="p-invalid" v-if="submitted && !product.name">Name is required.</small>
</div>
<div class="field">
<label for="description">Description</label>
<Textarea id="description" v-model="product.description" required="true" rows="3"
cols="20" />
</div>
<div class="field">
<label for="inventoryStatus" class="mb-3">Inventory Status</label>
<Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses"
optionLabel="label" placeholder="Select a Status">
<template #value="slotProps">
<div v-if="slotProps.value && slotProps.value.value">
<span :class="'product-badge status-' + slotProps.value.value">{{
slotProps.value.label }}</span>
</div>
<div v-else-if="slotProps.value && !slotProps.value.value">
<span :class="'product-badge status-' + slotProps.value.toLowerCase()">{{
slotProps.value }}</span>
</div>
<span v-else>
{{ slotProps.placeholder }}
</span>
</template>
</Dropdown>
</div>
<div class="field">
<label class="mb-3">Category</label>
<div class="formgrid grid">
<div class="field-radiobutton col-6">
<RadioButton id="category1" name="category" value="Accessories"
v-model="product.category" />
<label for="category1">Accessories</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category2" name="category" value="Clothing"
v-model="product.category" />
<label for="category2">Clothing</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category3" name="category" value="Electronics"
v-model="product.category" />
<label for="category3">Electronics</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category4" name="category" value="Fitness"
v-model="product.category" />
<label for="category4">Fitness</label>
</div>
</div>
</div>
<div class="formgrid grid">
<div class="field col">
<label for="price">Price</label>
<InputNumber id="price" v-model="product.price" mode="currency" currency="USD"
locale="en-US" :invalid="submitted && !product.price" :required="true" />
<small class="p-invalid" v-if="submitted && !product.price">Price is required.</small>
</div>
<div class="field col">
<label for="quantity">Quantity</label>
<InputNumber id="quantity" v-model="product.quantity" integeronly />
</div>
</div>
<template #footer>
<Button label="Cancel" icon="pi pi-times" text="" @click="hideDialog" />
<Button label="Save" icon="pi pi-check" text="" @click="saveProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductDialog" :style="{ width: '450px' }" header="Confirm"
:modal="true">
<div class="flex align-items-center justify-content-center">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete <b>{{ product.name }}</b>?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" text @click="deleteProductDialog = false" />
<Button label="Yes" icon="pi pi-check" text @click="deleteProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductsDialog" :style="{ width: '450px' }" header="Confirm"
:modal="true">
<div class="flex align-items-center justify-content-center">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete the selected products?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" text @click="deleteProductsDialog = false" />
<Button label="Yes" icon="pi pi-check" text @click="deleteSelectedProducts" />
</template>
</Dialog>
</div>
</div>
</div>
</app-layout>
</template>

View File

@ -0,0 +1,360 @@
<script setup>
import { onMounted, reactive, ref, watch } from 'vue';
import { ProductService } from '@/service/ProductService';
import { useLayout } from '@/Layouts/composables/layout';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
const { isDarkTheme } = useLayout();
const products = ref(null);
const lineData = reactive({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'First Dataset',
data: [65, 59, 80, 81, 56, 55, 40],
fill: false,
backgroundColor: '#2f4860',
borderColor: '#2f4860',
tension: 0.4
},
{
label: 'Second Dataset',
data: [28, 48, 40, 19, 86, 27, 90],
fill: false,
backgroundColor: '#00bb7e',
borderColor: '#00bb7e',
tension: 0.4
}
]
});
const items = ref([
{ label: 'Add New', icon: 'pi pi-fw pi-plus' },
{ label: 'Remove', icon: 'pi pi-fw pi-minus' }
]);
const lineOptions = ref(null);
const productService = new ProductService();
onMounted(() => {
productService.getProductsSmall().then((data) => (products.value = data));
});
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const applyLightTheme = () => {
lineOptions.value = {
plugins: {
legend: {
labels: {
color: '#495057'
}
}
},
scales: {
x: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
},
y: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
}
}
};
};
const applyDarkTheme = () => {
lineOptions.value = {
plugins: {
legend: {
labels: {
color: '#ebedef'
}
}
},
scales: {
x: {
ticks: {
color: '#ebedef'
},
grid: {
color: 'rgba(160, 167, 181, .3)'
}
},
y: {
ticks: {
color: '#ebedef'
},
grid: {
color: 'rgba(160, 167, 181, .3)'
}
}
}
};
};
watch(
isDarkTheme,
(val) => {
if (val) {
applyDarkTheme();
} else {
applyLightTheme();
}
},
{ immediate: true }
);
</script>
<template>
<authenticated-layout>
<div class="grid">
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Orders</span>
<div class="text-900 font-medium text-xl">152</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-shopping-cart text-blue-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">24 new </span>
<span class="text-500">since last visit</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Revenue</span>
<div class="text-900 font-medium text-xl">$2.100</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-map-marker text-orange-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">%52+ </span>
<span class="text-500">since last week</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Customers</span>
<div class="text-900 font-medium text-xl">28441</div>
</div>
<div class="flex align-items-center justify-content-center bg-cyan-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-inbox text-cyan-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">520 </span>
<span class="text-500">newly registered</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Comments</span>
<div class="text-900 font-medium text-xl">152 Unread</div>
</div>
<div class="flex align-items-center justify-content-center bg-purple-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-comment text-purple-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">85 </span>
<span class="text-500">responded</span>
</div>
</div>
<div class="col-12 xl:col-6">
<div class="card">
<h5>Recent Sales</h5>
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
<Column style="width: 15%">
<template #header> Image </template>
<template #body="slotProps">
<img :src="'demo/images/product/' + slotProps.data.image" :alt="slotProps.data.image" width="50" class="shadow-2" />
</template>
</Column>
<Column field="name" header="Name" :sortable="true" style="width: 35%"></Column>
<Column field="price" header="Price" :sortable="true" style="width: 35%">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column style="width: 15%">
<template #header> View </template>
<template #body>
<Button icon="pi pi-search" type="button" class="p-button-text"></Button>
</template>
</Column>
</DataTable>
</div>
<div class="card">
<div class="flex justify-content-between align-items-center mb-5">
<h5>Best Selling Products</h5>
<div>
<Button icon="pi pi-ellipsis-v" class="p-button-text p-button-plain p-button-rounded" @click="$refs.menu2.toggle($event)"></Button>
<Menu ref="menu2" :popup="true" :model="items"></Menu>
</div>
</div>
<ul class="list-none p-0 m-0">
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Space T-Shirt</span>
<div class="mt-1 text-600">Clothing</div>
</div>
<div class="mt-2 md:mt-0 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-orange-500 h-full" style="width: 50%"></div>
</div>
<span class="text-orange-500 ml-3 font-medium">%50</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Portal Sticker</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-cyan-500 h-full" style="width: 16%"></div>
</div>
<span class="text-cyan-500 ml-3 font-medium">%16</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Supernova Sticker</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-pink-500 h-full" style="width: 67%"></div>
</div>
<span class="text-pink-500 ml-3 font-medium">%67</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Wonders Notebook</span>
<div class="mt-1 text-600">Office</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-green-500 h-full" style="width: 35%"></div>
</div>
<span class="text-green-500 ml-3 font-medium">%35</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Mat Black Case</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-purple-500 h-full" style="width: 75%"></div>
</div>
<span class="text-purple-500 ml-3 font-medium">%75</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Robots T-Shirt</span>
<div class="mt-1 text-600">Clothing</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-teal-500 h-full" style="width: 40%"></div>
</div>
<span class="text-teal-500 ml-3 font-medium">%40</span>
</div>
</li>
</ul>
</div>
</div>
<div class="col-12 xl:col-6">
<div class="card">
<h5>Sales Overview</h5>
<Chart type="line" :data="lineData" :options="lineOptions" />
</div>
<div class="card">
<div class="flex align-items-center justify-content-between mb-4">
<h5>Notifications</h5>
<div>
<Button icon="pi pi-ellipsis-v" class="p-button-text p-button-plain p-button-rounded" @click="$refs.menu1.toggle($event)"></Button>
<Menu ref="menu1" :popup="true" :model="items"></Menu>
</div>
</div>
<span class="block text-600 font-medium mb-3">TODAY</span>
<ul class="p-0 mx-0 mt-0 mb-4 list-none">
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-blue-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-dollar text-xl text-blue-500"></i>
</div>
<span class="text-900 line-height-3"
>Richard Jones
<span class="text-700">has purchased a blue t-shirt for <span class="text-blue-500">79$</span></span>
</span>
</li>
<li class="flex align-items-center py-2">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-orange-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-download text-xl text-orange-500"></i>
</div>
<span class="text-700 line-height-3">Your request for withdrawal of <span class="text-blue-500 font-medium">2500$</span> has been initiated.</span>
</li>
</ul>
<span class="block text-600 font-medium mb-3">YESTERDAY</span>
<ul class="p-0 m-0 list-none">
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-blue-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-dollar text-xl text-blue-500"></i>
</div>
<span class="text-900 line-height-3"
>Keyser Wick
<span class="text-700">has purchased a black jacket for <span class="text-blue-500">59$</span></span>
</span>
</li>
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-pink-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-question text-xl text-pink-500"></i>
</div>
<span class="text-900 line-height-3"
>Jane Davis
<span class="text-700">has posted a new questions about your product.</span>
</span>
</li>
</ul>
</div>
<div
class="px-4 py-5 shadow-2 flex flex-column md:flex-row md:align-items-center justify-content-between mb-3"
style="border-radius: 1rem; background: linear-gradient(0deg, rgba(0, 123, 255, 0.5), rgba(0, 123, 255, 0.5)), linear-gradient(92.54deg, #1c80cf 47.88%, #ffffff 100.01%)"
>
<div>
<div class="text-blue-100 font-medium text-xl mt-2 mb-3">TAKE THE NEXT STEP</div>
<div class="text-white font-medium text-5xl">Try PrimeBlocks</div>
</div>
<div class="mt-4 mr-auto md:mt-0 md:mr-0">
<a href="https://www.primefaces.org/primeblocks-vue" class="p-button font-bold px-5 py-3 p-button-warning p-button-rounded p-button-raised"> Get Started </a>
</div>
</div>
</div>
</div>
</authenticated-layout>
</template>

View File

@ -0,0 +1,46 @@
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import DeleteUserForm from './Partials/DeleteUserForm.vue';
import UpdatePasswordForm from './Partials/UpdatePasswordForm.vue';
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm.vue';
import { Head } from '@inertiajs/vue3';
defineProps({
mustVerifyEmail: {
type: Boolean,
},
status: {
type: String,
},
});
</script>
<template>
<Head title="Profile" />
<AuthenticatedLayout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">Profile</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<UpdateProfileInformationForm
:must-verify-email="mustVerifyEmail"
:status="status"
class="max-w-xl"
/>
</div>
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<UpdatePasswordForm class="max-w-xl" />
</div>
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<DeleteUserForm class="max-w-xl" />
</div>
</div>
</div>
</AuthenticatedLayout>
</template>

View File

@ -0,0 +1,95 @@
<script setup>
import DangerButton from '@/Components/DangerButton.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import Modal from '@/Components/Modal.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import { nextTick, ref } from 'vue';
const confirmingUserDeletion = ref(false);
const passwordInput = ref(null);
const form = useForm({
password: '',
});
const confirmUserDeletion = () => {
confirmingUserDeletion.value = true;
nextTick(() => passwordInput.value.focus());
};
const deleteUser = () => {
form.delete(route('profile.destroy'), {
preserveScroll: true,
onSuccess: () => closeModal(),
onError: () => passwordInput.value.focus(),
onFinish: () => form.reset(),
});
};
const closeModal = () => {
confirmingUserDeletion.value = false;
form.reset();
};
</script>
<template>
<section class="space-y-6">
<header>
<h2 class="text-lg font-medium text-gray-900">Delete Account</h2>
<p class="mt-1 text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting
your account, please download any data or information that you wish to retain.
</p>
</header>
<DangerButton @click="confirmUserDeletion">Delete Account</DangerButton>
<Modal :show="confirmingUserDeletion" @close="closeModal">
<div class="p-6">
<h2 class="text-lg font-medium text-gray-900">
Are you sure you want to delete your account?
</h2>
<p class="mt-1 text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Please
enter your password to confirm you would like to permanently delete your account.
</p>
<div class="mt-6">
<InputLabel for="password" value="Password" class="sr-only" />
<TextInput
id="password"
ref="passwordInput"
v-model="form.password"
type="password"
class="mt-1 block w-3/4"
placeholder="Password"
@keyup.enter="deleteUser"
/>
<InputError :message="form.errors.password" class="mt-2" />
</div>
<div class="mt-6 flex justify-end">
<SecondaryButton @click="closeModal"> Cancel </SecondaryButton>
<DangerButton
class="ms-3"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
@click="deleteUser"
>
Delete Account
</DangerButton>
</div>
</div>
</Modal>
</section>
</template>

View File

@ -0,0 +1,105 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
const passwordInput = ref(null);
const currentPasswordInput = ref(null);
const form = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const updatePassword = () => {
form.put(route('password.update'), {
preserveScroll: true,
onSuccess: () => form.reset(),
onError: () => {
if (form.errors.password) {
form.reset('password', 'password_confirmation');
passwordInput.value.focus();
}
if (form.errors.current_password) {
form.reset('current_password');
currentPasswordInput.value.focus();
}
},
});
};
</script>
<template>
<section>
<header>
<h2 class="text-lg font-medium text-gray-900">Update Password</h2>
<p class="mt-1 text-sm text-gray-600">
Ensure your account is using a long, random password to stay secure.
</p>
</header>
<form @submit.prevent="updatePassword" class="mt-6 space-y-6">
<div>
<InputLabel for="current_password" value="Current Password" />
<TextInput
id="current_password"
ref="currentPasswordInput"
v-model="form.current_password"
type="password"
class="mt-1 block w-full"
autocomplete="current-password"
/>
<InputError :message="form.errors.current_password" class="mt-2" />
</div>
<div>
<InputLabel for="password" value="New Password" />
<TextInput
id="password"
ref="passwordInput"
v-model="form.password"
type="password"
class="mt-1 block w-full"
autocomplete="new-password"
/>
<InputError :message="form.errors.password" class="mt-2" />
</div>
<div>
<InputLabel for="password_confirmation" value="Confirm Password" />
<TextInput
id="password_confirmation"
v-model="form.password_confirmation"
type="password"
class="mt-1 block w-full"
autocomplete="new-password"
/>
<InputError :message="form.errors.password_confirmation" class="mt-2" />
</div>
<div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton>
<Transition
enter-active-class="transition ease-in-out"
enter-from-class="opacity-0"
leave-active-class="transition ease-in-out"
leave-to-class="opacity-0"
>
<p v-if="form.recentlySuccessful" class="text-sm text-gray-600">Saved.</p>
</Transition>
</div>
</form>
</section>
</template>

View File

@ -0,0 +1,102 @@
<script setup>
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Link, useForm, usePage } from '@inertiajs/vue3';
defineProps({
mustVerifyEmail: {
type: Boolean,
},
status: {
type: String,
},
});
const user = usePage().props.auth.user;
const form = useForm({
name: user.name,
email: user.email,
});
</script>
<template>
<section>
<header>
<h2 class="text-lg font-medium text-gray-900">Profile Information</h2>
<p class="mt-1 text-sm text-gray-600">
Update your account's profile information and email address.
</p>
</header>
<form @submit.prevent="form.patch(route('profile.update'))" class="mt-6 space-y-6">
<div>
<InputLabel for="name" value="Name" />
<TextInput
id="name"
type="text"
class="mt-1 block w-full"
v-model="form.name"
required
autofocus
autocomplete="name"
/>
<InputError class="mt-2" :message="form.errors.name" />
</div>
<div>
<InputLabel for="email" value="Email" />
<TextInput
id="email"
type="email"
class="mt-1 block w-full"
v-model="form.email"
required
autocomplete="username"
/>
<InputError class="mt-2" :message="form.errors.email" />
</div>
<div v-if="mustVerifyEmail && user.email_verified_at === null">
<p class="text-sm mt-2 text-gray-800">
Your email address is unverified.
<Link
:href="route('verification.send')"
method="post"
as="button"
class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Click here to re-send the verification email.
</Link>
</p>
<div
v-show="status === 'verification-link-sent'"
class="mt-2 font-medium text-sm text-green-600"
>
A new verification link has been sent to your email address.
</div>
</div>
<div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton>
<Transition
enter-active-class="transition ease-in-out"
enter-from-class="opacity-0"
leave-active-class="transition ease-in-out"
leave-to-class="opacity-0"
>
<p v-if="form.recentlySuccessful" class="text-sm text-gray-600">Saved.</p>
</Transition>
</div>
</form>
</section>
</template>

View File

@ -0,0 +1,363 @@
<script setup>
import { onMounted, reactive, ref, watch } from 'vue';
import { ProductService } from '@/service/ProductService';
import { useLayout } from '@/Layouts/composables/layout';
import AppLayout from '@/Layouts/AppLayout.vue';
const { isDarkTheme } = useLayout();
const products = ref(null);
const lineData = reactive({
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'First Dataset',
data: [65, 59, 80, 81, 56, 55, 40],
fill: false,
backgroundColor: '#2f4860',
borderColor: '#2f4860',
tension: 0.4
},
{
label: 'Second Dataset',
data: [28, 48, 40, 19, 86, 27, 90],
fill: false,
backgroundColor: '#00bb7e',
borderColor: '#00bb7e',
tension: 0.4
}
]
});
const items = ref([
{ label: 'Add New', icon: 'pi pi-fw pi-plus' },
{ label: 'Remove', icon: 'pi pi-fw pi-minus' }
]);
const lineOptions = ref(null);
const productService = new ProductService();
onMounted(() => {
productService.getProductsSmall().then((data) => (products.value = data));
});
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const applyLightTheme = () => {
lineOptions.value = {
plugins: {
legend: {
labels: {
color: '#495057'
}
}
},
scales: {
x: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
},
y: {
ticks: {
color: '#495057'
},
grid: {
color: '#ebedef'
}
}
}
};
};
const applyDarkTheme = () => {
lineOptions.value = {
plugins: {
legend: {
labels: {
color: '#ebedef'
}
}
},
scales: {
x: {
ticks: {
color: '#ebedef'
},
grid: {
color: 'rgba(160, 167, 181, .3)'
}
},
y: {
ticks: {
color: '#ebedef'
},
grid: {
color: 'rgba(160, 167, 181, .3)'
}
}
}
};
};
watch(
isDarkTheme,
(val) => {
if (val) {
applyDarkTheme();
} else {
applyLightTheme();
}
},
{ immediate: true }
);
</script>
<template>
<AppLayout>
<div class="grid">
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Orders</span>
<div class="text-900 font-medium text-xl">152</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-shopping-cart text-blue-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">24 new </span>
<span class="text-500">since last visit</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Revenue</span>
<div class="text-900 font-medium text-xl">$2.100</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-map-marker text-orange-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">%52+ </span>
<span class="text-500">since last week</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Customers</span>
<div class="text-900 font-medium text-xl">28441</div>
</div>
<div class="flex align-items-center justify-content-center bg-cyan-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-inbox text-cyan-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">520 </span>
<span class="text-500">newly registered</span>
</div>
</div>
<div class="col-12 lg:col-6 xl:col-3">
<div class="card mb-0">
<div class="flex justify-content-between mb-3">
<div>
<span class="block text-500 font-medium mb-3">Comments</span>
<div class="text-900 font-medium text-xl">152 Unread</div>
</div>
<div class="flex align-items-center justify-content-center bg-purple-100 border-round" style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-comment text-purple-500 text-xl"></i>
</div>
</div>
<span class="text-green-500 font-medium">85 </span>
<span class="text-500">responded</span>
</div>
</div>
<div class="col-12 xl:col-6">
<div class="card">
<h5>Recent Sales</h5>
<DataTable :value="products" :rows="5" :paginator="true" responsiveLayout="scroll">
<Column style="width: 15%">
<template #header> Image </template>
<template #body="slotProps">
<img :src="'demo/images/product/' + slotProps.data.image" :alt="slotProps.data.image" width="50" class="shadow-2" />
</template>
</Column>
<Column field="name" header="Name" :sortable="true" style="width: 35%"></Column>
<Column field="price" header="Price" :sortable="true" style="width: 35%">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column style="width: 15%">
<template #header> View </template>
<template #body>
<Button icon="pi pi-search" type="button" class="p-button-text"></Button>
</template>
</Column>
</DataTable>
</div>
<div class="card">
<div class="flex justify-content-between align-items-center mb-5">
<h5>Best Selling Products</h5>
<div>
<Button icon="pi pi-ellipsis-v" class="p-button-text p-button-plain p-button-rounded" @click="$refs.menu2.toggle($event)"></Button>
<Menu ref="menu2" :popup="true" :model="items"></Menu>
</div>
</div>
<ul class="list-none p-0 m-0">
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Space T-Shirt</span>
<div class="mt-1 text-600">Clothing</div>
</div>
<div class="mt-2 md:mt-0 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-orange-500 h-full" style="width: 50%"></div>
</div>
<span class="text-orange-500 ml-3 font-medium">%50</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Portal Sticker</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-cyan-500 h-full" style="width: 16%"></div>
</div>
<span class="text-cyan-500 ml-3 font-medium">%16</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Supernova Sticker</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-pink-500 h-full" style="width: 67%"></div>
</div>
<span class="text-pink-500 ml-3 font-medium">%67</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Wonders Notebook</span>
<div class="mt-1 text-600">Office</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-green-500 h-full" style="width: 35%"></div>
</div>
<span class="text-green-500 ml-3 font-medium">%35</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Mat Black Case</span>
<div class="mt-1 text-600">Accessories</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-purple-500 h-full" style="width: 75%"></div>
</div>
<span class="text-purple-500 ml-3 font-medium">%75</span>
</div>
</li>
<li class="flex flex-column md:flex-row md:align-items-center md:justify-content-between mb-4">
<div>
<span class="text-900 font-medium mr-2 mb-1 md:mb-0">Robots T-Shirt</span>
<div class="mt-1 text-600">Clothing</div>
</div>
<div class="mt-2 md:mt-0 ml-0 md:ml-8 flex align-items-center">
<div class="surface-300 border-round overflow-hidden w-10rem lg:w-6rem" style="height: 8px">
<div class="bg-teal-500 h-full" style="width: 40%"></div>
</div>
<span class="text-teal-500 ml-3 font-medium">%40</span>
</div>
</li>
</ul>
</div>
</div>
<div class="col-12 xl:col-6">
<div class="card">
<h5>Sales Overview</h5>
<Chart type="line" :data="lineData" :options="lineOptions" />
</div>
<div class="card">
<div class="flex align-items-center justify-content-between mb-4">
<h5>Notifications</h5>
<div>
<Button icon="pi pi-ellipsis-v" class="p-button-text p-button-plain p-button-rounded" @click="$refs.menu1.toggle($event)"></Button>
<Menu ref="menu1" :popup="true" :model="items"></Menu>
</div>
</div>
<span class="block text-600 font-medium mb-3">TODAY</span>
<ul class="p-0 mx-0 mt-0 mb-4 list-none">
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-blue-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-dollar text-xl text-blue-500"></i>
</div>
<span class="text-900 line-height-3"
>Richard Jones
<span class="text-700">has purchased a blue t-shirt for <span class="text-blue-500">79$</span></span>
</span>
</li>
<li class="flex align-items-center py-2">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-orange-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-download text-xl text-orange-500"></i>
</div>
<span class="text-700 line-height-3">Your request for withdrawal of <span class="text-blue-500 font-medium">2500$</span> has been initiated.</span>
</li>
</ul>
<span class="block text-600 font-medium mb-3">YESTERDAY</span>
<ul class="p-0 m-0 list-none">
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-blue-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-dollar text-xl text-blue-500"></i>
</div>
<span class="text-900 line-height-3"
>Keyser Wick
<span class="text-700">has purchased a black jacket for <span class="text-blue-500">59$</span></span>
</span>
</li>
<li class="flex align-items-center py-2 border-bottom-1 surface-border">
<div class="w-3rem h-3rem flex align-items-center justify-content-center bg-pink-100 border-circle mr-3 flex-shrink-0">
<i class="pi pi-question text-xl text-pink-500"></i>
</div>
<span class="text-900 line-height-3"
>Jane Davis
<span class="text-700">has posted a new questions about your product.</span>
</span>
</li>
</ul>
</div>
<div
class="px-4 py-5 shadow-2 flex flex-column md:flex-row md:align-items-center justify-content-between mb-3"
style="border-radius: 1rem; background: linear-gradient(0deg, rgba(0, 123, 255, 0.5), rgba(0, 123, 255, 0.5)), linear-gradient(92.54deg, #1c80cf 47.88%, #ffffff 100.01%)"
>
<div>
<div class="text-blue-100 font-medium text-xl mt-2 mb-3">TAKE THE NEXT STEP</div>
<div class="text-white font-medium text-5xl">Try PrimeBlocks</div>
</div>
<div class="mt-4 mr-auto md:mt-0 md:mr-0">
<a href="https://www.primefaces.org/primeblocks-vue" class="p-button font-bold px-5 py-3 p-button-warning p-button-rounded p-button-raised"> Get Started </a>
</div>
</div>
</div>
</div>
</AppLayout>
</template>

38
resources/js/app.js Normal file
View File

@ -0,0 +1,38 @@
import './bootstrap';
// import '../css/app.css';
import '../assets/styles.scss';
// import 'primevue/resources/themes/aura-light-green/theme.css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import PrimeVue from 'primevue/config';
import Aura from '../css/presets/aura';
import router from '../router';
import ToastService from 'primevue/toastservice';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, App, props, plugin }) {
return createApp({ render: () => h(App, props) })
.use(plugin)
.use(PrimeVue, {
unstyled: false,
ripple: true,
// pt: Aura
})
.use(ToastService)
.use(router)
.use(ZiggyVue)
.mount(el);
},
progress: {
color: '#4B5563',
},
});

4
resources/js/bootstrap.js vendored Normal file
View File

@ -0,0 +1,4 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

View File

@ -0,0 +1,7 @@
export class CountryService {
getCountries() {
return fetch('/demo/data/countries.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
}

View File

@ -0,0 +1,18 @@
export class CustomerService {
getCustomersSmall() {
return fetch('/demo/data/customers-small.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getCustomersMedium() {
return fetch('/demo/data/customers-medium.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getCustomersLarge() {
return fetch('/demo/data/customers-large.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
}

View File

@ -0,0 +1,37 @@
export class NodeService {
getFiles() {
return fetch('/demo/data/files.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getLazyFiles() {
return fetch('/demo/data/files-lazy.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getFilesystem() {
return fetch('/demo/data/filesystem.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getLazyFilesystem() {
return fetch('/demo/data/filesystem-lazy.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getTreeTableNodes() {
return fetch('/demo/data/treetablenodes.json')
.then((res) => res.json())
.then((d) => d.root);
}
getTreeNodes() {
return fetch('/demo/data/treenodes.json')
.then((res) => res.json())
.then((d) => d.root);
}
}

View File

@ -0,0 +1,7 @@
export class PhotoService {
getImages() {
return fetch('/demo/data/photos.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
}

View File

@ -0,0 +1,31 @@
export class ProductService {
getProductsSmall() {
return fetch('/demo/data/products-small.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getProducts() {
return fetch('/demo/data/products.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getProductsMixed() {
return fetch('/demo/data/products-mixed.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getProductsWithOrdersSmall() {
return fetch('/demo/data/products-orders-small.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
getProductsWithOrdersLarge() {
return fetch('/demo/data/products-orders.json', { headers: { 'Cache-Control': 'no-cache' } })
.then((res) => res.json())
.then((d) => d.data);
}
}