No changes made

This commit is contained in:
2025-04-16 12:47:04 -04:00
commit 1ed3b0c2d4
98 changed files with 8857 additions and 0 deletions

22
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<Toast />
<TopMenubar />
<div class="flex flex-row m-10 justify-around">
<router-view />
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue'
const TopMenubar = defineAsyncComponent(() => import('./components/TopMenubar.vue'));
const Toast = defineAsyncComponent(() => import('primevue/toast'));
</script>
<style>
@import "tailwindcss";
html, body {
font-family: 'Nunito', sans-serif;
}
</style>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

View File

@@ -0,0 +1,109 @@
<template>
<div>
<Menubar :model="items">
<template #start>
<router-link to="/">
Load Data Manager
</router-link>
<i class="ml-3 mr-3 fa-thin fa-gun"></i>
::
</template>
</Menubar>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MenuItem } from 'primevue/menuitem'
import { useRouter } from 'vue-router'
import { icons } from '../lib/icons.ts'
const Menubar = defineAsyncComponent(() => import('primevue/menubar'))
const router = useRouter()
const items = ref<MenuItem[]>([
{
label: 'Loads',
items: [
{
label: 'New',
command: () => router.push('/loads/add'),
icon: icons.add
},
{
label: 'Edit/Search',
command: () => router.push('/loads/search'),
icon: icons.edit
}
]
},
{
label: 'Components',
items: [
{
label: 'Bullets',
items: [
{
label: 'New',
command: () => router.push('/bullets/add'),
icon: icons.add
},
{
label: 'List',
command: () => router.push('/bullets'),
icon: icons.list
}
]
},
{
label: 'Powders',
items: [
{
label: 'New',
command: () => router.push('/powders/add'),
icon: icons.add
},
{
label: 'List',
command: () => router.push('/powders'),
icon: icons.list
}
]
},
{
label: 'Primers',
items: [
{
label: 'New',
command: () => router.push('/primers/add'),
icon: icons.add
},
{
label: 'List',
command: () => router.push('/primers'),
icon: icons.list
}
]
},
]
},
{
label: 'Manufacturers',
items: [
{
label: 'Manage Manufacturers',
command: () => router.push('/manufacturers'),
icon: icons.add
},
{
label: 'Add Manufacturer',
command: () => router.push('/manufacturers/add'),
icon: icons.edit
}
]
},
{
separator: true
}
])
</script>

View File

@@ -0,0 +1,8 @@
export const icons = {
add: 'fa-thin fa-sharp fa-plus',
edit: 'fa-thin fa-sharp fa-pencil',
list: 'fa-thin fa-sharp fa-list',
upload: 'fa-thin fa-sharp fa-upload',
save: 'fa-thin fa-sharp fa-floppy-disk',
delete: 'fa-thin fa-sharp fa-trash',
}

20
frontend/src/main.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createApp } from 'vue'
import Aura from '@primeuix/themes/aura'
import App from './App.vue'
import router from './router'
import PrimeVue from 'primevue/config'
const app = createApp(App)
app.use(router)
app.use(PrimeVue, {
theme: {
preset: Aura,
},
})
import ToastService from 'primevue/toastservice'
app.use(ToastService)
app.mount('#app')

View File

@@ -0,0 +1,16 @@
<template>
<Card>
<template #title> Welcome to Your Load Database </template>
<template #content>
<p>
This is the place to track and create loads for all of your ammunition. Use the links at the top to navigate.
</p>
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue'
const Card = defineAsyncComponent(() => import('primevue/card'))
</script>

View File

@@ -0,0 +1,166 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>Add New Bullet</template>
<template #content>
<div class="w-full">
<label>Manufacturer</label>
<Dropdown :options="manufacturers" option-label="name" option-value="id" v-model="bullet.manufacturer_id"
class="w-full" />
</div>
<div class="m-5" v-if="bullet.manufacturer_id === '-- Add --'">
<div class="w-full m-1">
<label>Manufacturer Name</label>
<div class="w-full">
<InputText v-model="add.name" class="w-full" />
</div>
</div>
<div class="w-full m-1">
<label>Manufacturer Url</label>
<div class="w-full">
<InputText v-model="add.url" class="w-full" />
</div>
</div>
</div>
<div class="w-full mt-3">
<label>Bullet Name</label>
<InputText v-model="bullet.name" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Bullet Diameter</label>
<InputNumber v-model="bullet.diameter" prefix="." :min="0" :max="999" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Grain Weight</label>
<InputNumber v-model="bullet.weight" suffix=" gr" :min="0" :max="1000" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
</div>
</template>
<template #footer>
<div class="mt-3">
<Button :loading="loading" @click="create" label="Add" :icon="icons.add" />
</div>
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import axios, { AxiosError } from 'axios'
import { Manufacturer } from '../../types/manufacturer'
import { Response } from '../../types/Response'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import { Bullet } from '../../types/bullet'
import { ToastMessageOptions } from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
import { useRouter } from 'vue-router'
import { icons } from '../../lib/icons.ts'
const toast = useToast()
const router = useRouter()
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const Card = defineAsyncComponent(() => import('primevue/card'))
const add = ref({
name: '',
url: '',
})
const bullet = ref({
name: '',
diameter: 0,
manufacturer_id: '',
weight: 0,
meta: {},
})
const file = ref('')
const manufacturers = ref<Manufacturer[]>([])
const loading = ref(false)
const loadManufacturers = async () => {
await axios.get(`${import.meta.env.VITE_API}/manufacturer`).then((r: Response<Manufacturer[]>) => {
manufacturers.value.push({
name: '-- Add --',
id: '-- Add --',
} as Manufacturer)
if (!r.data.payload) {
return
}
r.data.payload.forEach((m: Manufacturer) => {
manufacturers.value.push(m)
})
})
}
const fileSelected = (event: FileUploadSelectEvent) => {
file.value = event.files[0]
}
const createManufacturer = async () => {
return axios.post(`${import.meta.env.VITE_API}/manufacturer`, {
name: add.value.name,
url: add.value.url,
}, {
headers: {
Authorization: `Bearer ${jwt}`,
},
}).then((r: Response<Manufacturer>) => {
manufacturers.value.push(r.data.payload)
bullet.value.manufacturer_id = r.data.payload.id
})
}
const create = async () => {
loading.value = true
if (bullet.value.manufacturer_id === '-- Add --') {
try {
await createManufacturer()
} catch (e) {
toast.add({
severity: 'error',
summary: 'Error',
detail: e,
life: 3000,
} as ToastMessageOptions)
return
}
}
let formData = new FormData()
formData.append('photo', file.value)
formData.append('manufacturer_id', bullet.value.manufacturer_id)
formData.append('weight', bullet.value.weight.toString())
formData.append('name', bullet.value.name)
formData.append('diameter', bullet.value.diameter.toString())
axios.post(`${import.meta.env.VITE_API}/bullet`,
formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
},
).then((r: Response<string>) => {
router.push(`/bullets/edit/${r.data.payload}`)
}).catch((e: AxiosError<{ message: string }>) => {
loading.value = false
toast.add({
severity: 'error',
summary: 'Error',
detail: e.response?.data.message,
life: 3000,
} as ToastMessageOptions)
})
}
loadManufacturers()
</script>

View File

@@ -0,0 +1,178 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Edit Bullet
</template>
<template #content>
<div class="grid-cols-1">
<div>
<label>Manufacturer</label>
<Dropdown
class="w-full"
:options="manufacturers"
option-label="name"
option-value="id"
v-model="bullet.manufacturer_id"
/>
</div>
<div class="mt-3">
<label>Bullet Name</label>
<InputText class="w-full" v-model="bullet.name" />
</div>
<div class="w-full mt-3">
<label>Bullet Diameter</label>
<InputNumber v-model="bullet.diameter" prefix="." :min="0" :max="999" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Grain Weight</label>
<InputNumber v-model="bullet.weight" suffix=" gr" :min="0" :max="1000" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
<template #uploadicon>
<i :class="icons.upload" class="mr-3" />
</template>
</FileUpload>
<div class="mt-3">
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
</div>
</div>
</div>
</template>
<template #footer>
<Button
type="button"
:loading="loading"
label="Save"
@click="save"
:icon="icons.save"
class="mr-3"
/>
<Button
severity="danger"
label="Delete"
:icon="icons.delete"
@click="deleteBullet"
/>
</template>
</Card>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from 'vue'
import axios from 'axios'
import { Manufacturer } from '../../types/manufacturer'
import { Response } from '../../types/Response'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import { useRoute, useRouter } from 'vue-router'
import { useToast } from 'primevue/usetoast'
import { icons } from '../../lib/icons.ts'
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Image = defineAsyncComponent(() => import('primevue/image'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const Card = defineAsyncComponent(() => import('primevue/card'))
interface BulletResponse {
id: string
name: string
diameter: number
weight: number
manufacturer: {
id: string
name: string
url: string
}
}
const route = useRoute()
const router = useRouter()
const toast = useToast()
const loading = ref(false)
const bullet = ref({
diameter: 0,
weight: 0,
name: '',
manufacturer_id: '',
})
const file = ref('')
const fileSelected = (event: FileUploadSelectEvent) => {
console.log(event.files[0])
file.value = event.files[0]
}
const manufacturers = ref<Manufacturer[]>([])
const fetchManufacturers = async () => {
const response = await axios.get<any, Response<Manufacturer[]>>(`${import.meta.env.VITE_API}/manufacturer`)
manufacturers.value = response.data.payload
}
const pictureUrl = computed(() => {
const cache = new Date().getMilliseconds()
return import.meta.env.VITE_API + `/bullet/${route.params.id}/photo?cache=${cache}`
})
const deleteBullet = () => {
axios.delete(`${import.meta.env.VITE_API}/bullet/${route.params.id}`).then(() => {
router.push('/bullets')
}).catch((error) => {
toast.add({
severity: 'error',
summary: 'Error',
detail: error.response.data.message,
})
})
}
const get = async () => {
const response = await axios.get<any, Response<BulletResponse>>(
`${import.meta.env.VITE_API}/bullet/${route.params.id}`)
bullet.value.manufacturer_id = response.data.payload.manufacturer.id
bullet.value.name = response.data.payload.name
bullet.value.diameter = response.data.payload.diameter
bullet.value.weight = response.data.payload.weight
}
const save = async () => {
loading.value = true
let formData = new FormData()
formData.append('photo', file.value)
formData.append('manufacturer_id', bullet.value.manufacturer_id)
formData.append('weight', bullet.value.weight.toString())
formData.append('name', bullet.value.name)
formData.append('diameter', bullet.value.diameter.toString())
axios.post(`${import.meta.env.VITE_API}/bullet/${route.params.id}`,
formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
},
).then(() => {
loading.value = false
toast.add({
severity: 'success',
summary: 'saved!',
detail: 'bullet saved',
life: 3000,
})
get()
}).catch((e) => {
toast.add({
severity: 'error',
summary: 'error',
detail: e.response?.data.message,
life: 3000,
})
loading.value = false
})
}
fetchManufacturers()
get()
</script>

View File

@@ -0,0 +1,48 @@
<template>
<Card class="w-full md:w-2/3">
<template #title>
Bullets
</template>
<template #content>
<DataTable :value="bullets">
<template #empty>
<div class="p-text-center p-text-secondary p-text-bold p-text-uppercase">No bullets found</div>
</template>
<Column header="Bullet">
<template #body="slotProps">
{{ slotProps.data.manufacturer.name }}
{{ slotProps.data.weight }}gr
{{ slotProps.data.name }}
</template>
</Column>
<Column header="Edit">
<template #body="slotProps">
<router-link class="p-button p-button-link p-button-sm" :to="`/bullets/edit/${slotProps.data.id}`">
<i :class="icons.edit"></i>
</router-link>
</template>
</Column>
</DataTable>
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import axios from 'axios'
import { Bullet } from '../../types/bullet'
import {Response} from "../../types/Response";
import Column from 'primevue/column'
import { icons } from '../../lib/icons.ts'
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
const Card = defineAsyncComponent(() => import('primevue/card'))
const bullets = ref<Bullet[]>([])
const fetchBullets = async () => {
const response = await axios.get<any, Response<Bullet[]>>(`${import.meta.env.VITE_API}/bullet`)
bullets.value = response.data.payload
}
fetchBullets()
</script>

View File

@@ -0,0 +1,297 @@
<template>
<Card class="md:w-2/3 w-full">
<template #title>
Add New Load
</template>
<template #content>
<div class="grid grid-cols-1">
<div>
Cartridge
</div>
<div class="flex flex-row items-center">
<div class="grow mr-3">
<Select v-model="load.cartridge" :options="cartridges" optionLabel="label" optionValue="value"
class="w-full" />
<Message v-if="v$.$dirty && v$.cartridge.$invalid" :value="false" size="small" severity="error"
variant="simple">Cartridge Required
</Message>
</div>
<div class="cursor-pointer" @click="addCartridgeDialog = true">
<i :class="icons.add" class="fa-2x" />
</div>
</div>
<div class="mt-3 mr-3">
Bullet
</div>
<div class="w-full mt-3">
<Select v-model="load.bullet" :invalid="v$.$dirty && v$.bullet.$invalid" :options="bullets"
optionLabel="label" optionValue="value" class="w-full" />
<Message v-if="v$.$dirty && v$.bullet.$invalid" :value="false" size="small" severity="error" variant="simple">
Bullet Required
</Message>
</div>
<div class="mt-3 mr-3">
Powder
</div>
<div class="flex flex-row">
<div class="grow mr-3">
<Select v-model="load.powder" :options="powders" optionLabel="label" optionValue="value" class="w-full" />
<Message v-if="v$.$dirty && v$.powder.$invalid" :value="false" size="small" severity="error"
variant="simple">Powder Required
</Message>
</div>
<div>
<InputNumber v-model="load.powderGrs" placeholder="00.0 gr" suffix=" gr" :minFractionDigits="1"
:maxFractionDigits="1" :min="0"
:max="1000" class="w-full" />
<Message v-if="v$.$dirty && v$.powderGrs.$invalid" :value="false" size="small" severity="error"
variant="simple">Grains Required
</Message>
</div>
</div>
<div class="mt-3 mr-3">
Primer
</div>
<div class="w-full mt-3">
<Select v-model="load.primer" :options="primers" optionLabel="label" optionValue="value" class="w-full" />
<Message v-if="v$.$dirty && v$.primer.$invalid" :value="false" size="small" severity="error"
variant="simple">Primer Required
</Message>
</div>
<div class="mt-3 mr-3">
COL
</div>
<div class="w-full mt-3">
<InputMask placeholder="0.000" v-model="load.col" mask="9.999" class="w-full" />
<Message v-if="v$.$dirty && v$.col.$invalid" :value="false" size="small" severity="error"
variant="simple">COL Required
</Message>
</div>
<div class="w-full mt-5">
<label>Picture</label>
<Message v-if="v$.$dirty && !file" :value="false" size="small" severity="error"
variant="simple">Picture Required
</Message>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
</div>
</div>
</template>
<template #footer>
<Button label="Add" :icon="icons.add" @click="add" />
</template>
</Card>
<Dialog @update:visible="addCartridgeDialog = false" modal :visible="addCartridgeDialog" :style="{ width: '35rem' }">
<template #header>
Add Cartridge
</template>
<div class="grid grid-cols-1">
<div>
Name
</div>
<div class="w-full">
<InputText :invalid="cartridgeName === ''" v-model="cartridgeName" class="w-full" />
</div>
</div>
<template #footer>
<Button :disabled="cartridgeName === ''" label="Add" :icon="icons.add" @click="addCartridgeName" />
</template>
</Dialog>
</template>
<script lang="ts">
interface Select {
label: string
value: string
}
interface Cartridge {
id: string
name: string
}
</script>
<script lang="ts" setup>
import { Bullet } from '../../types/bullet'
import { defineAsyncComponent, onMounted, ref } from 'vue'
import { Primers } from '../../types/primers'
import { Powder } from '../../types/powder'
import axios from 'axios'
import { Response } from '../../types/Response'
import { icons } from '../../lib/icons.ts'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import { useToast } from 'primevue/usetoast'
import router from '../../router'
const Card = defineAsyncComponent(() => import('primevue/card'))
const Select = defineAsyncComponent(() => import('primevue/select'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const InputNumber = defineAsyncComponent(() => import('primevue/inputnumber'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const InputMask = defineAsyncComponent(() => import('primevue/inputmask'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Dialog = defineAsyncComponent(() => import('primevue/dialog'))
const Message = defineAsyncComponent(() => import('primevue/message'))
const toast = useToast()
const bullets = ref<Select[]>([])
const primers = ref<Select[]>([])
const powders = ref<Select[]>([])
const cartridges = ref<Select[]>([])
const cartridgeName = ref('')
const addCartridgeDialog = ref(false)
const file = ref<File | null>(null)
const fileSelected = (e: FileUploadSelectEvent) => {
file.value = e.files[0]
}
const load = ref({
bullet: '',
cartridge: '',
powder: '',
powderGrs: 0,
primer: '',
col: '',
})
const v$ = useVuelidate({
bullet: { required },
cartridge: { required },
powder: { required },
powderGrs: {
required, min: function (val: number | null) {
if (!val) return false
return val > 0
},
},
primer: { required },
col: {
required, min: function (val: string | null) {
if (!val) return false
// regex to validate COL does not container _
return !val.includes('_')
},
},
}, load.value)
const fetchBullets = async () => {
const response = await axios.get<any, Response<Bullet[]>>(`${import.meta.env.VITE_API}/bullet`)
response.data.payload.forEach((bullet: Bullet) => {
bullets.value.push({ label: `${bullet.manufacturer.name} ${bullet.weight}gr ${bullet.name}`, value: bullet.id })
})
}
const fetchPrimers = async () => {
const response = await axios.get<any, Response<Primers[]>>(`${import.meta.env.VITE_API}/primer`)
response.data.payload.forEach((primer: Primers) => {
primers.value.push({ label: `${primer.manufacturer.name} ${primer.name}`, value: primer.id })
})
}
const fetchPowders = async () => {
const response = await axios.get<any, Response<Powder[]>>(`${import.meta.env.VITE_API}/powder`)
response.data.payload.forEach((powder: Powder) => {
powders.value.push({ label: `${powder.manufacturer.name} ${powder.name}`, value: powder.id })
})
}
const fetchCartridges = async () => {
cartridges.value = []
const response = await axios.get<any, Response<Cartridge[]>>(`${import.meta.env.VITE_API}/cartridge`)
response.data.payload.forEach((cartridge: Cartridge) => {
cartridges.value.push({ label: `${cartridge.name}`, value: cartridge.id })
})
}
const addCartridgeName = async () => {
if (cartridgeName.value === '') {
return
}
try {
await axios.post<any, Response<Cartridge>>(`${import.meta.env.VITE_API}/cartridge`, {
name: cartridgeName.value,
})
} catch (error) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Error adding cartridge',
})
}
await fetchCartridges()
cartridgeName.value = ''
addCartridgeDialog.value = false
}
const add = async () => {
v$.value.$touch()
if (v$.value.$invalid || !file.value) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Please correct all fields',
life: 3000,
})
return
}
const formData = new FormData()
formData.append('bullet_id', load.value.bullet)
formData.append('cartridge_id', load.value.cartridge)
formData.append('powder_id', load.value.powder)
formData.append('powder_gr', load.value.powderGrs.toString())
formData.append('primer_id', load.value.primer)
formData.append('col', load.value.col)
formData.append('photo', file.value)
try {
const response = await axios.post<any, Response<string>>(`${import.meta.env.VITE_API}/load`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Load added',
life: 3000,
})
await router.push(`/loads/edit/${response.data.payload}`)
} catch (error) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Error adding load',
life: 3000,
})
}
}
onMounted(() => {
fetchBullets()
fetchPrimers()
fetchPowders()
fetchCartridges()
})
</script>

View File

@@ -0,0 +1,140 @@
<template>
<Card class="w-11/12">
<template #title>
Loads
</template>
<template #content>
<DataTable
:value="loads"
filterDisplay="row"
paginator
lazy
:rows="50"
:totalRecords="total"
:loading="loading"
>
<template #empty>
<div class="p-4">
<p>No loads found</p>
</div>
</template>
<Column field="cartridge" header="Name" :sortable="true" :showFilterMenu="false">
<template #filter>
<Select />
</template>
<template #body="{ data }">
{{ data.cartridge }}
</template>
</Column>
<Column field="bulletManufacturer" header="Bullet Manufacturer" :sortable="true" :showFilterMenu="false">
<template #filter>
<Select />
</template>
<template #body="{ data }">
{{ data.bullet.manufacturer.name }}
</template>
</Column>
<Column header="Bullet" :sortable="true" :showFilterMenu="false">
<template #filter>
<Select />
</template>
<template #body="{ data }">
{{ data.bullet.name }}
{{ data.bullet.weight }}gr
.{{ data.bullet.diameter }}
</template>
</Column>
<Column field="name" header="Primer Manufacturer" :sortable="true">
<template #body="{ data }">
{{ data.primer.manufacturer.name }}
</template>
</Column>
<Column field="name" header="Primer" :sortable="true">
<template #body="{ data }">
{{ data.primer.name }}
</template>
</Column>
<Column field="name" header="Powder Manufacturer" :sortable="true">
<template #body="{ data }">
{{ data.powder.manufacturer.name }}
</template>
</Column>
<Column field="name" header="Powder" :sortable="true">
<template #body="{ data }">
{{ data.powder.name }}
</template>
</Column>
<Column field="name" header="Powder Grs" :sortable="true">
<template #body="{ data }">
{{ data.powder_gr }}gr
</template>
</Column>
<Column field="name" header="Edit">
<template #body="{ data }">
<Button size="small" text :icon="icons.edit" />
</template>
</Column>
</DataTable>
</template>
</Card>
</template>
<script lang="ts">
import { Bullet } from '../../types/bullet'
import { Powder } from '../../types/powder'
import { Primers } from '../../types/primers'
interface LoadResponse {
total: number
results: Load[]
}
interface Load {
id: string
bullet: Bullet
cartridge: string
powder: Powder
powder_gr: number
primer: Primers
col: number
}
</script>
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, ref } from 'vue'
import { Response } from '../../types/Response'
import axios from 'axios'
import Column from 'primevue/column'
import { icons } from '../../lib/icons.ts'
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
const Card = defineAsyncComponent(() => import('primevue/card'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const Select = defineAsyncComponent(() => import('primevue/select'))
const loads = ref<Load[]>([])
const total = ref(0)
const page = ref(1)
const loading = ref(true)
const fetchLoads = async () => {
loading.value = true
const resp = await axios.get<any, Response<LoadResponse>>(`${import.meta.env.VITE_API}/load`)
loads.value = resp.data.payload.results
total.value = resp.data.payload.total
loading.value = false
}
onMounted(() => {
fetchLoads()
})
</script>

View File

@@ -0,0 +1,72 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Add Manufacturer
</template>
<template #content>
<div>
<div>
<label for="name">Name</label>
</div>
<InputText id="name" v-model="manufacturer.name" class="w-full" />
</div>
<div class="mt-4">
<label for="url">Url</label>
</div>
<InputText id="url" v-model="manufacturer.url" class="w-full" />
</template>
<template #footer>
<div class="flex justify-end mt-4">
<Button label="Add" @click="add" :loading="loading" :icon="icons.add" />
</div>
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import axios from 'axios'
import { Response } from '../../types/Response'
import { useToast } from 'primevue/usetoast'
import { useRouter } from 'vue-router'
import { icons } from '../../lib/icons.ts'
const Card = defineAsyncComponent(() => import('primevue/card'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const toast = useToast()
const router = useRouter()
const loading = ref(false)
const manufacturer = ref({
name: '',
url: '',
})
const add = async () => {
loading.value = true
axios.post(`${import.meta.env.VITE_API}/manufacturer`, {
name: manufacturer.value.name,
url: manufacturer.value.url,
}).then((response: Response<string>) => {
toast.add({
severity: 'success',
summary: 'Added Manufacturer',
life: 3000,
})
if (response) {
router.push(`/manufacturers/edit/${response.data.payload}`)
}
}).finally(() => {
loading.value = false
}).catch((e) => {
toast.add({
severity: 'error',
summary: 'Something went wrong',
detail: e.response?.data.message,
life: 3000,
})
})
}
</script>

View File

@@ -0,0 +1,100 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Edit Manufacturer
</template>
<template #content>
<div>
<div>
<label for="name">Name</label>
</div>
<InputText id="name" v-model="manufacturer.name" class="w-full" />
</div>
<div class="mt-4">
<label for="url">Url</label>
</div>
<InputText id="url" v-model="manufacturer.url" class="w-full" />
</template>
<template #footer>
<div class="flex justify-end mt-4">
<Button label="Save" @click="save" :loading="loading" :icon="icons.save" />
<Button severity="danger" label="Delete" @click="del" class="ml-2" :icon="icons.delete" />
</div>
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, ref } from 'vue'
import axios from "axios";
import {Response} from "../../types/Response";
import {Manufacturer} from "../../types/manufacturer";
import { useToast } from 'primevue/usetoast'
import { useRoute, useRouter } from 'vue-router'
import { icons } from '../../lib/icons.ts'
const Card = defineAsyncComponent(() => import('primevue/card'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const router = useRouter()
const toast = useToast()
const route = useRoute()
const loading = ref(false)
const manufacturer = ref({
name: '',
url: '',
})
const get = async () => {
loading.value = true
axios.get(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`).then((response: Response<Manufacturer>) => {
manufacturer.value.name = response.data.payload.name
manufacturer.value.url = response.data.payload.url
})
.finally(() => {
loading.value = false
})
}
const del = async () => {
axios.delete(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`).then(() => {
router.push({path: '/manufacturers'})
}).catch((e) => {
toast.add({
severity: 'error',
summary: 'Something went wrong',
detail: e.response?.data.message,
life: 3000
})
})
}
const save = async () => {
loading.value = true
axios.post(`${import.meta.env.VITE_API}/manufacturer/${route.params.id}`, {
name: manufacturer.value.name,
url: manufacturer.value.url,
}).finally(() => {
loading.value = false
}).catch((e) => {
toast.add({
severity: 'error',
summary: 'Something went wrong',
detail: e.response?.data.message,
life: 3000
})
}).then(() => {
toast.add({
severity: 'success',
summary: 'Successfully Saved',
life: 3000
})
})
}
onMounted(() => {
get()
})
</script>

View File

@@ -0,0 +1,56 @@
<template>
<div>
<div>
<DataTable size="small" :value="manufacturers" tableStyle="min-width: 50rem">
<template #empty>
<div class="p-text-center">
<p>No manufacturers found.</p>
</div>
</template>
<Column header="Name">
<template #body="slotProps">
<a v-if="slotProps.data.url" target="_blank" :href="slotProps.data.url">{{ slotProps.data.name }}</a>
<span v-else>{{ slotProps.data.name }}</span>
</template>
</Column>
<Column header="Edit">
<template #body="slotProps">
<router-link
:to="`/manufacturers/edit/${slotProps.data.id}`"
class="p-button p-button-link p-button-sm"
>Edit</router-link>
</template>
</Column>
</DataTable>
</div>
<div class="mt-5">
<Button @click="router.push('/manufacturers/add')" label="Add" :icon="icons.add" />
</div>
</div>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import axios from "axios";
import {Response} from "../../types/Response";
import {Manufacturer} from "../../types/manufacturer";
import Column from 'primevue/column';
import { useRouter } from 'vue-router'
import { icons } from '../../lib/icons.ts'
const DataTable = defineAsyncComponent(() => import('primevue/datatable'));
const Button = defineAsyncComponent(() => import('primevue/button'));
const router = useRouter()
const manufacturers = ref<Manufacturer[]>([])
const fetchManufacturers = async () => {
axios.get(`${import.meta.env.VITE_API}/manufacturer`).then((response: Response<Manufacturer[]>) => {
if (response.data.payload) {
manufacturers.value = response.data.payload
}
})
}
fetchManufacturers()
</script>

View File

@@ -0,0 +1,95 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Add Powder
</template>
<template #content>
<div class="grid grid-cols-1 gap-6">
<div class="w-full">
<label>Manufacturer</label>
<Dropdown :options="manufacturers" option-label="name" option-value="id" v-model="powder.manufacturer_id"
class="w-full" />
</div>
<div>
<label>Name</label>
<InputText v-model="powder.name" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
</div>
</div>
</template>
<template #footer>
<Button :loading="loading" label="Add" @click="add" :icon="icons.save" />
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, ref } from 'vue'
import { fetchManufacturers } from '../../services/manufacturers.ts'
import { Manufacturer } from '../../types/manufacturer'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import axios from 'axios'
import router from '../../router'
import { useToast } from 'primevue/usetoast'
import { Response } from '../../types/Response'
import { icons } from '../../lib/icons.ts'
const Card = defineAsyncComponent(() => import('primevue/card'))
const Dropdown = defineAsyncComponent(() => import('primevue/dropdown'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const toast = useToast()
const manufacturers = ref<Manufacturer[]>([])
const file = ref<File | null>(null)
const loading = ref(false)
const powder = ref({
manufacturer_id: '',
name: '',
})
const fileSelected = (event: FileUploadSelectEvent) => {
file.value = event.files[0]
}
const add = () => {
loading.value = true
const formData = new FormData()
formData.append('manufacturer_id', powder.value.manufacturer_id)
formData.append('name', powder.value.name)
if (file.value) {
formData.append('photo', file.value)
}
axios.post(`${import.meta.env.VITE_API}/powder`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}).
then((r: Response<{ id: string }>) => {
router.push(`/powders/edit/${r.data.payload.id}`)
}).catch(() => {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'An error occurred while adding the powder',
life: 3000,
})
}).finally(() => {
loading.value = false
})
}
onMounted(async () => {
const resp = await fetchManufacturers()
manufacturers.value.push(...resp.payload)
})
</script>

View File

@@ -0,0 +1,133 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
<h2>Edit Powder</h2>
</template>
<template #content>
<div v-if="powder" class="flex flex-wrap flex-col md:flex-row">
<div class="w-full mt-5">
<label>Manufacturer</label>
<Select :options="manufacturers" option-label="name" option-value="id" v-model="powder.manufacturer.id"
class="w-full" />
</div>
<div class="w-full mt-5">
<label for="name">Name</label>
<InputText v-model="powder.name" type="text" id="name" class="w-full" />
</div>
<div class="mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
<template #uploadicon>
<i :class="icons.upload" class="mr-3"></i>
</template>
</FileUpload>
<div class="mt-3">
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
</div>
</div>
</div>
</template>
<template #footer>
<Button class="mr-3" :loading="loading" @click="update" label="Save" :icon="icons.save" />
<Button severity="danger" :loading="loading" @click="deletePowder" label="Delete" :icon="icons.delete" />
</template>
</Card>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import axios from 'axios'
import { useRoute, useRouter } from 'vue-router'
import { Response } from '../../types/Response'
import { Powder } from '../../types/powder'
import { Manufacturer } from '../../types/manufacturer'
import { fetchManufacturers } from '../../services/manufacturers.ts'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import { useToast } from 'primevue/usetoast'
import { icons } from '../../lib/icons.ts'
const route = useRoute()
const router = useRouter()
const toast = useToast()
const Card = defineAsyncComponent(() => import('primevue/card'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Select = defineAsyncComponent(() => import('primevue/select'))
const Image = defineAsyncComponent(() => import('primevue/image'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const powder = ref<Powder | null>(null)
const manufacturers = ref<Manufacturer[]>([])
const loading = ref(false)
const getPowder = async () => {
axios.get(`${import.meta.env.VITE_API}/powder/${route.params.id}`).then((response: Response<Powder>) => {
powder.value = response.data.payload
}).catch((error) => {
console.log(error)
})
}
const file = ref('')
const fileSelected = (event: FileUploadSelectEvent) => {
file.value = event.files[0]
}
const pictureUrl = computed(() => {
const cache = new Date().getMilliseconds()
return import.meta.env.VITE_API + `/powder/${route.params.id}/photo?cache=${cache}`
})
const deletePowder = () => {
axios.delete(`${import.meta.env.VITE_API}/powder/${route.params.id}`).then(() => {
router.push('/powders')
}).catch((error) => {
toast.add({
severity: 'error',
summary: 'Error',
detail: error.response.data.message,
})
})
}
const update = () => {
loading.value = true
let formData = new FormData()
formData.append('photo', file.value)
formData.append('name', powder.value?.name || '')
formData.append('manufacturer_id', powder.value?.manufacturer.id || '')
axios.post(`${import.meta.env.VITE_API}/powder/${route.params.id}`,
formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
},
).then(() => {
loading.value = false
toast.add({
severity: 'success',
summary: 'saved!',
detail: 'powder saved',
life: 3000,
})
getPowder()
}).catch(() => {
loading.value = false
toast.add({
severity: 'error',
summary: 'error!',
detail: 'powder not saved',
life: 3000,
})
})
}
onMounted(async () => {
getPowder().then()
const resp = await fetchManufacturers()
manufacturers.value.push(...resp.payload)
})
</script>

View File

@@ -0,0 +1,57 @@
<template>
<Card class="w-full md:w-2/3">
<template #title>
<h2>Powders</h2>
</template>
<template #content>
<DataTable :value="powders">
<template #empty>
<div class="p-4">
<p>No powders found</p>
</div>
</template>
<Column field="manufacturer" header="Name">
<template #body="{data}">
<span>{{ data.manufacturer.name }} {{ data.name }}</span>
</template>
</Column>
<Column field="actions" header="Actions">
<template #body="{data}">
<Button
text
size="small"
:icon="icons.edit"
@click="router.push(`/powders/edit/${data.id}`)"
/>
</template>
</Column>
</DataTable>
</template>
</Card>
</template>
<script lang="ts" setup>
import axios from 'axios'
import { Response } from '../../types/Response'
import { Powder } from '../../types/powder'
import { defineAsyncComponent, ref } from 'vue'
import Column from 'primevue/column'
import { useRouter } from 'vue-router'
import { icons } from '../../lib/icons.ts'
const Card = defineAsyncComponent(() => import('primevue/card'))
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const powders = ref<Powder[]>([])
const router = useRouter()
const get = () => {
axios.get(`${import.meta.env.VITE_API}/powder`).then((r: Response<Powder[]>) => {
powders.value = r.data.payload
})
}
get()
</script>

View File

@@ -0,0 +1,93 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Add Primer
</template>
<template #content>
<div class="grid grid-cols-1 gap-6">
<div class="w-full">
<label>Manufacturer</label>
<Select :options="manufacturers" option-label="name" option-value="id" v-model="primer.manufacturer_id"
class="w-full" />
</div>
<div>
<label>Name</label>
<InputText v-model="primer.name" class="w-full" />
</div>
<div class="w-full mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload />
</div>
</div>
</template>
<template #footer>
<Button :loading="loading" label="Add" :icon="icons.add" @click="add" />
</template>
</Card>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, ref } from 'vue'
import { Manufacturer } from '../../types/manufacturer'
import { fetchManufacturers } from '../../services/manufacturers.ts'
import axios from 'axios'
import { icons } from '../../lib/icons.ts'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import { Response } from '../../types/Response'
import router from '../../router'
import { useToast } from 'primevue/usetoast'
const toast = useToast()
const Card = defineAsyncComponent(() => import('primevue/card'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Select = defineAsyncComponent(() => import('primevue/select'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const manufacturers = ref<Manufacturer[]>([])
const primer = ref({
manufacturer_id: '',
name: '',
})
const file = ref<File | null>(null)
const loading = ref(false)
const fileSelected = (event: FileUploadSelectEvent) => {
file.value = event.files[0]
}
const add = () => {
loading.value = true
const formData = new FormData()
formData.append('manufacturer_id', primer.value.manufacturer_id)
formData.append('name', primer.value.name)
if (file.value) {
formData.append('photo', file.value)
}
axios.post(`${import.meta.env.VITE_API}/primer`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}).
then((r: Response<{ id: string }>) => {
router.push(`/primers/edit/${r.data.payload.id}`)
}).catch(() => {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'An error occurred while adding the powder',
life: 3000,
})
}).finally(() => {
loading.value = false
})
}
onMounted(async () => {
const resp = await fetchManufacturers()
manufacturers.value.push(...resp.payload)
})
</script>

View File

@@ -0,0 +1,142 @@
<template>
<Card class="w-full md:w-1/2">
<template #title>
Edit Primer
</template>
<template #content>
<div v-if="primer" class="flex flex-row flex-wrap">
<div class="w-full">
<label>Manufacturer</label>
<Select :options="manufacturers" option-label="name" option-value="id" v-model="primer.manufacturer.id"
class="w-full"
/>
</div>
<div class="w-full mt-5">
<label for="name">Name</label>
<InputText v-model="primer.name" type="text" id="name" class="w-full" />
</div>
<div class="mt-3">
<label>Picture</label>
<FileUpload v-model="file" mode="basic" @select="fileSelected" customUpload>
<template #uploadicon>
<i :class="icons.upload" class="mr-3"></i>
</template>
</FileUpload>
<div class="mt-3">
<Image v-if="!loading" :src="pictureUrl" alt="picture" />
</div>
</div>
</div>
</template>
<template #footer>
<Button class="mr-3" label="Save" :icon="icons.save" @click="update" />
<Button severity="danger" label="Delete" :icon="icons.delete" @click="deletePrimer" />
</template>
</Card>
</template>
<script lang="ts" setup>
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import { Manufacturer } from '../../types/manufacturer'
import { fetchManufacturers } from '../../services/manufacturers.ts'
import { Response} from '../../types/Response'
import { Primers } from '../../types/primers'
import { icons } from '../../lib/icons.ts'
import { FileUploadSelectEvent } from 'primevue/fileupload'
import { useToast } from 'primevue/usetoast'
const route = useRoute()
const router = useRouter()
const toast = useToast()
const Card = defineAsyncComponent(() => import('primevue/card'))
const Select = defineAsyncComponent(() => import('primevue/select'))
const InputText = defineAsyncComponent(() => import('primevue/inputtext'))
const Image = defineAsyncComponent(() => import('primevue/image'))
const FileUpload = defineAsyncComponent(() => import('primevue/fileupload'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const manufacturers = ref<Manufacturer[]>([])
const primer = ref<Primers | null>(null)
const loading = ref(false)
const file = ref<File | null>(null)
const fileSelected = (event: FileUploadSelectEvent) => {
file.value = event.files[0]
}
const pictureUrl = computed(() => {
const cache = new Date().getMilliseconds()
return import.meta.env.VITE_API + `/primer/${route.params.id}/photo?cache=${cache}`
})
const deletePrimer = () => {
axios.delete(`${import.meta.env.VITE_API}/primer/${route.params.id}`).then(() => {
router.push('/primers')
}).catch((error) => {
toast.add({
severity: 'error',
summary: 'Error',
detail: error.response.data.message,
life: 3000
})
})
}
const getPrimer = () => {
axios.get(`${import.meta.env.VITE_API}/primer/${route.params.id}`)
.then((response: Response<Primers>) => {
primer.value = response.data.payload
}).catch((error) => {
console.log(error)
})
}
const update = () => {
loading.value = true
if (!primer.value) {
return
}
const formData = new FormData()
formData.append('name', primer.value.name)
formData.append('manufacturer_id', primer.value.manufacturer.id)
if (file.value) {
formData.append('photo', file.value)
}
axios.post(`${import.meta.env.VITE_API}/primer/${route.params.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
}).then(() => {
file.value = null
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Primer updated successfully',
life: 3000
})
})
.catch((error) => {
toast.add({
severity: 'error',
summary: 'Error',
detail: error.response.data.message,
life: 3000
})
}).finally(() => {
loading.value = false
})
}
getPrimer()
onMounted(async () => {
const response = await fetchManufacturers()
manufacturers.value = response.payload
})
</script>

View File

@@ -0,0 +1,61 @@
<template>
<Card class="w-2/3">
<template #title>
Primers
</template>
<template #content>
<DataTable :value="primers">
<template #empty>
<div class="p-4">
<p>No primers found</p>
</div>
</template>
<Column field="name" header="Name">
<template #body="{ data }">
{{ data.manufacturer.name }} {{ data.name }}
</template>
</Column>
<Column field="name" header="Edit">
<template #body="{ data }">
<Button
text
small
color="primary"
:icon="icons.edit"
@click="router.push(`/primers/edit/${data.id}`)"
/>
</template>
</Column>
</DataTable>
</template>
</Card>
</template>
<script lang="ts" setup>
import axios from 'axios'
import { defineAsyncComponent, ref } from 'vue'
import Column from 'primevue/column'
import { Response } from '../../types/Response'
import { Primers } from '../../types/primers'
import { icons } from '../../lib/icons.ts'
import { useRouter } from 'vue-router'
const router = useRouter()
const Card = defineAsyncComponent(() => import('primevue/card'))
const Button = defineAsyncComponent(() => import('primevue/button'))
const DataTable = defineAsyncComponent(() => import('primevue/datatable'))
const primers = ref<Primers[]>([])
const getPrimers = () => {
axios.get(`${import.meta.env.VITE_API}/primer`)
.then((response: Response<Primers[]>) => {
primers.value = response.data.payload
})
.catch((error) => {
console.error(error)
})
}
getPrimers()
</script>

View File

@@ -0,0 +1,71 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../pages/Index.vue'),
},
{
path: '/bullets/add',
component: () => import('../pages/bullets/Add.vue'),
},
{
path: '/bullets/edit/:id',
component: () => import('../pages/bullets/Edit.vue'),
},
{
path: '/bullets',
component: () => import('../pages/bullets/List.vue'),
},
{
path: '/powders',
component: () => import('../pages/powders/List.vue'),
},
{
path: '/powders/add',
component: () => import('../pages/powders/Add.vue'),
},
{
path: '/powders/edit/:id',
component: () => import('../pages/powders/Edit.vue'),
},
{
path: '/primers',
component: () => import('../pages/primers/List.vue'),
},
{
path: '/primers/add',
component: () => import('../pages/primers/Add.vue'),
},
{
path: '/primers/edit/:id',
component: () => import('../pages/primers/Edit.vue'),
},
{
path: '/manufacturers',
component: () => import('../pages/manufacturers/List.vue'),
},
{
path: '/manufacturers/add',
component: () => import('../pages/manufacturers/Add.vue'),
},
{
path: '/manufacturers/edit/:id',
component: () => import('../pages/manufacturers/Edit.vue'),
},
{
path: '/loads/add',
component: () => import('../pages/loads/Add.vue'),
},
{
path: '/loads/search',
component: () => import('../pages/loads/Search.vue'),
},
] as RouteRecordRaw[]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router

View File

@@ -0,0 +1,10 @@
import axios from 'axios'
import { Response } from '../types/Response'
import { Manufacturer } from '../types/manufacturer'
export const fetchManufacturers = async () => {
const response = await axios.get<any, Response<Manufacturer[]>>(
`${import.meta.env.VITE_API}/manufacturer`)
return response.data
}

8
frontend/src/types/Response.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import {AxiosResponse} from "axios";
export interface Response<T> extends AxiosResponse {
data: {
payload: T
status: 'OK' | 'ERROR'
}
}

8
frontend/src/types/bullet.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import { Manufacturer } from './manufacturer'
export interface Bullet {
id: string
name: string
weight: number
manufacturer: Manufacturer
}

5
frontend/src/types/manufacturer.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export interface Manufacturer {
id: string
name: string
url: string
}

8
frontend/src/types/powder.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import { Manufacturer } from './manufacturer'
export interface Powder {
id: string;
name: string;
meta: string;
manufacturer: Manufacturer;
}

7
frontend/src/types/primers.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import { Manufacturer } from './manufacturer'
export interface Primers {
id: string
name: string
manufacturer: Manufacturer
}

10
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
import {ComponentCustomProperties as GCS} from "@vue/runtime-core";
import {Account} from "appwrite";
declare module "@vue/runtime-core" {
interface ComponentCustomProperties extends GCS {
$account: Account
}
}