updated page layouts and format

This commit is contained in:
2025-07-30 11:32:53 -04:00
parent 7358bb1a4c
commit 156d77850a
9 changed files with 1538 additions and 35 deletions

1405
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.6", "vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4" "vue-tsc": "^3.0.4"
} }
} }

BIN
src/assets/guns.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

View File

@@ -53,7 +53,7 @@
<div class="w-full mt-5"> <div class="w-full mt-5">
<div class="flex gap-2" style="flex-direction: column"> <div class="flex gap-2" style="flex-direction: column">
<label for="date_purchased">Date Purchased</label> <label for="date_purchased">Date Purchased</label>
<Calendar date-format="mm/dd/yy" v-model="gun.date_purchased"/> <DatePicker date-format="mm/dd/yy" v-model="gun.date_purchased"/>
</div> </div>
</div> </div>
<div class="w-full mt-5"> <div class="w-full mt-5">
@@ -98,7 +98,7 @@ const router = useRouter()
const ConfirmPopup = defineAsyncComponent(() => import('primevue/confirmpopup')) const ConfirmPopup = defineAsyncComponent(() => import('primevue/confirmpopup'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext')) const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber')) const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
const Calendar = defineAsyncComponent(() => import('primevue/calendar')) const DatePicker = defineAsyncComponent(() => import('primevue/datepicker'))
const Textarea = defineAsyncComponent(() => import('primevue/textarea')) const Textarea = defineAsyncComponent(() => import('primevue/textarea'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload')) const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Button = defineAsyncComponent(() => import('primevue/button')) const Button = defineAsyncComponent(() => import('primevue/button'))

21
src/localstorage/index.ts Normal file
View File

@@ -0,0 +1,21 @@
export function SaveToLocalStorage<T>(key: string, value: T): void {
try {
const serializedValue = JSON.stringify(value);
localStorage.setItem(key, serializedValue);
} catch (error) {
console.error("Error saving to localStorage", error);
}
}
export function GetFromLocalStorage<T>(key: string): T | null {
try {
const serializedValue = localStorage.getItem(key);
if (serializedValue === null) {
return null;
}
return JSON.parse(serializedValue) as T;
} catch (error) {
console.error("Error reading from localStorage", error);
return null;
}
}

View File

@@ -1,9 +1,10 @@
<template> <template>
<div class="w-full flex flex-row flex-wrap justify-around"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div class="w-full lg:w-1/2 m-5"> <div class="w-full">
<GunForm :loading="loading" @upload="getGun" v-if="gun" @save="save" :gun="gun"/> <GunForm :loading="loading" @upload="getGun" v-if="gun" @save="save" :gun="gun"/>
</div> </div>
<div class="mt-10 w-full lg:w-1/2 m-5">
<div class="">
<Card> <Card>
<template #content v-if="gun != null"> <template #content v-if="gun != null">
<h3 class="text-lg font-bold"> <h3 class="text-lg font-bold">
@@ -12,12 +13,16 @@
<div <div
class="p-5 mt-4 flex justify-evenly" v-for="photo in gun.photos"> class="p-5 mt-4 flex justify-evenly" v-for="photo in gun.photos">
<div> <div>
<Image preview zoomInDisabled zoomOutDisabled> <Image
<template #image> preview
<img alt="image" :src="`${remoteUrl}/gun/photo/${photo.id}/200/${photo.file_name}`" /> :src="`${remoteUrl}/gun/photo/${photo.id}/200/${photo.file_name}`"
</template> >
<template #preview> <template #original="slotProps">
<img :src="`${remoteUrl}/gun/photo/${photo.id}/${photo.file_name}`" alt="image"/> <img
:style="slotProps.style"
:src="`${remoteUrl}/gun/photo/${photo.id}/${photo.file_name}`"
alt="Gun Photo"
/>
</template> </template>
</Image> </Image>
</div> </div>
@@ -40,6 +45,7 @@ import {Response} from "../types/Response"
import {Guns} from "../types/guns"; import {Guns} from "../types/guns";
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useToast } from 'primevue' import { useToast } from 'primevue'
import { useConfirm } from 'primevue/useconfirm'
const GunForm = defineAsyncComponent(() => import('../components/GunForm.vue')) const GunForm = defineAsyncComponent(() => import('../components/GunForm.vue'))
const ConfirmPopup = defineAsyncComponent(() => import('primevue/confirmpopup')); const ConfirmPopup = defineAsyncComponent(() => import('primevue/confirmpopup'));
@@ -49,6 +55,7 @@ const Card = defineAsyncComponent(() => import('primevue/card'));
const route = useRoute() const route = useRoute()
const toast = useToast() const toast = useToast()
const confirm = useConfirm()
const loading = ref(false) const loading = ref(false)
const gun = ref<Guns | null>(null) const gun = ref<Guns | null>(null)
@@ -65,16 +72,29 @@ function getGun() {
} }
function deletePhoto(photoId: number) { function deletePhoto(photoId: number) {
confirm.require({
message: 'Are you sure you want to delete this photo?',
acceptClass: 'p-button-danger',
acceptIcon: 'fa-solid fa-trash',
rejectIcon: 'fa-solid fa-ban',
accept: () => {
deletePhotoConfirmed(photoId)
},
reject: () => {}
})
}
function deletePhotoConfirmed(photoId: number) {
axios.delete(import.meta.env.VITE_API + `/gun/photo/${photoId}`) axios.delete(import.meta.env.VITE_API + `/gun/photo/${photoId}`)
.then(() => { .then(() => {
toast.add({ toast.add({
severity: 'success', severity: 'success',
summary: 'Success', summary: 'Success',
detail: 'Photo deleted successfully', detail: 'Photo deleted successfully',
life: 3000 life: 3000
}) })
getGun() getGun()
}) })
} }
function save() { function save() {

View File

@@ -1,22 +1,39 @@
<template> <template>
<Card> <Card class="w-full">
<template #title> <template #title>
Gun List Gun List
</template> </template>
<template #content> <template #content>
<span class="p-input-icon-left w-full"> <span class="p-input-icon-left w-full">
<i class="fa-regular fa-magnifying-glass"></i> <InputGroup class="mb-2">
<InputText v-model="filters.global.value" class="w-full mb-3" placeholder="Search"/> <InputGroupAddon>
<i class="fa-solid fa-2x fa-magnifying-glass"></i>
</InputGroupAddon>
<InputText
size="small"
v-model="filters.global.value"
class="w-full mb-3"
placeholder="Search"
/>
</InputGroup>
</span> </span>
<DataTable <DataTable
sortField="make" :sortOrder="1" v-if="sortField"
:sortField="sortField.field"
:sortOrder="sortField.direction"
:globalFilterFields="['make', 'model']" :globalFilterFields="['make', 'model']"
:loading="loading" :loading="loading"
filterDisplay="row" v-model:filters="filters" class="p-datatable-sm" :value="guns"> filterDisplay="row"
<Column sortable field="id" header="ID"></Column> v-model:filters="filters"
<Column sortable field="make" header="Make"></Column> :value="guns"
<Column sortable field="model" header="Model"></Column> size="small"
<Column sortable field="value_amount" header="Value"> @sort="onSortChange"
>
<Column :sortable="true" field="id" header="ID"></Column>
<Column :sortable="true" field="make" header="Make"></Column>
<Column :sortable="true" field="model" header="Model"></Column>
<Column :sortable="true" field="value_amount" header="Value">
<template #body="slotProps"> <template #body="slotProps">
${{ slotProps.data.value_amount }} ${{ slotProps.data.value_amount }}
</template> </template>
@@ -38,19 +55,24 @@
</template> </template>
</Card> </Card>
</template> </template>
<script lang="ts">
const LocalStorageKey = 'sortField'
</script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, ref } from 'vue' import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import {Guns} from "../types/guns"; import {Guns} from "../types/guns";
import {Response} from "../types/Response"; import {Response} from "../types/Response";
import axios from 'axios' import axios from 'axios'
import {DataTableFilterMetaData} from "primevue/datatable"; import { DataTableFilterMetaData, DataTableSortEvent } from 'primevue/datatable'
import Column from 'primevue/column'; import Column from 'primevue/column';
import { GetFromLocalStorage, SaveToLocalStorage } from '../localstorage'
const Button = defineAsyncComponent(() => import('primevue/button')); const Button = defineAsyncComponent(() => import('primevue/button'));
const Card = defineAsyncComponent(() => import('primevue/card')); const Card = defineAsyncComponent(() => import('primevue/card'));
const InputText = defineAsyncComponent(() => import('primevue/inputtext')); const InputText = defineAsyncComponent(() => import('primevue/inputtext'));
const DataTable = defineAsyncComponent(() => import('primevue/datatable')); const DataTable = defineAsyncComponent(() => import('primevue/datatable'));
const InputGroup = defineAsyncComponent(() => import('primevue/inputgroup'));
const InputGroupAddon = defineAsyncComponent(() => import('primevue/inputgroupaddon'));
const guns = ref<Guns[]>([]) const guns = ref<Guns[]>([])
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
@@ -61,6 +83,12 @@ const filters = ref<{global: DataTableFilterMetaData}>({
} as DataTableFilterMetaData } as DataTableFilterMetaData
}) })
const sortField = ref<{ field: string, direction: number } | null>(GetFromLocalStorage(LocalStorageKey))
if (!sortField.value) {
sortField.value = { field: 'id', direction: 1 }
}
const total = computed(() => { const total = computed(() => {
let total = 0; let total = 0;
@@ -82,6 +110,26 @@ function fetchGuns() {
}) })
} }
function onSortChange(event: DataTableSortEvent) {
let field = event.sortField || 'id'
let direction = event.sortOrder || 1
if (typeof field !== 'string') {
field = 'id'
}
if (!direction || typeof direction === 'undefined') {
direction = 1
}
SaveToLocalStorage<{ field: string, direction: number }>(LocalStorageKey, {
field: field,
direction: direction
})
console.log('onSortChange', event)
}
onMounted(() => { onMounted(() => {
fetchGuns() fetchGuns()
}) })

View File

@@ -1,9 +1,10 @@
<template> <template>
<Card class="w-full lg:w-1/2 text-center"> <Card class="w-full md:w-1/2 text-center">
<template #title> Welcome to Your Gun Database </template> <template #title> Welcome to Your Gun Database</template>
<template #content> <template #content>
<img :src="imgUrl" alt="Gun Database Logo" />
<router-link to="/guns"> <router-link to="/guns">
<Button>Go To Gun List</Button> <Button class="mt-3">Go To Gun List</Button>
</router-link> </router-link>
</template> </template>
</Card> </Card>
@@ -11,6 +12,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import imgUrl from '../assets/guns.gif'
const Card = defineAsyncComponent(() => import('primevue/card')); const Card = defineAsyncComponent(() => import('primevue/card'));
const Button = defineAsyncComponent(() => import('primevue/button')); const Button = defineAsyncComponent(() => import('primevue/button'));
</script> </script>

View File

@@ -1,8 +1,13 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue(), tailwindcss()], plugins: [vue(), tailwindcss(), vueDevTools(
{
launchEditor: 'webstorm'
}
)],
}) })