mirror of
https://forgejo.merr.is/annika/isl-vue3.git
synced 2025-12-11 10:56:31 -05:00
Type Issues Fixed and First Pass New Item Form
This commit is contained in:
parent
a1aa171bb1
commit
aafd3c19de
15 changed files with 422 additions and 164 deletions
|
|
@ -22,6 +22,10 @@ COPY . .
|
||||||
|
|
||||||
# [optional] tests & build
|
# [optional] tests & build
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
# I can't run the type checking because of some sort of issue with
|
||||||
|
# bun and vue-tsc.
|
||||||
|
# see https://github.com/oven-sh/bun/issues/4754
|
||||||
|
# RUN bunx --bun vue-tsc --build --force
|
||||||
RUN bunx --bun vite build
|
RUN bunx --bun vite build
|
||||||
|
|
||||||
# Copy the distribution folder into the final image.
|
# Copy the distribution folder into the final image.
|
||||||
|
|
|
||||||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -18,7 +18,6 @@
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.7",
|
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vuetify": "^3.4.10"
|
"vuetify": "^3.4.10"
|
||||||
|
|
@ -43,6 +42,8 @@
|
||||||
"typescript": "~5.3.0",
|
"typescript": "~5.3.0",
|
||||||
"vite": "^5.0.10",
|
"vite": "^5.0.10",
|
||||||
"vitest": "^1.0.4",
|
"vitest": "^1.0.4",
|
||||||
"vue-tsc": "^1.8.25"
|
"vue-tsc": "^1.8.25",
|
||||||
|
"sass": "^1.69.7",
|
||||||
|
"vite-plugin-vuetify": "^2.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
160
src/components/NewItemForm.vue
Normal file
160
src/components/NewItemForm.vue
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
type EventItemListing = {
|
||||||
|
name: string
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstName = ref('')
|
||||||
|
const itemType = ref(0)
|
||||||
|
const itemName = ref('')
|
||||||
|
const iconURL = ref('')
|
||||||
|
const minPower = ref(0)
|
||||||
|
const maxPower = ref(0)
|
||||||
|
const rarity = ref(0)
|
||||||
|
const origin = ref('')
|
||||||
|
const tooltip = ref('')
|
||||||
|
const isEventItem = ref(false)
|
||||||
|
|
||||||
|
const eventItems = [
|
||||||
|
{
|
||||||
|
name: 'Blessing Power',
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Intimacy Power',
|
||||||
|
id: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fellow Power',
|
||||||
|
id: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const rarities = [
|
||||||
|
{
|
||||||
|
name: 'Green',
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Blue',
|
||||||
|
id: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Purple',
|
||||||
|
id: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yellow',
|
||||||
|
id: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Red',
|
||||||
|
id: 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const yesNo = [
|
||||||
|
{
|
||||||
|
name: 'Yes',
|
||||||
|
id: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No',
|
||||||
|
id: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Yes, I do want any, I don't know WHAT they have typed, but this rule is only checking that they typed SOMETHING.
|
||||||
|
const requiredField = (v: any) => (v ? true : 'This field is required.')
|
||||||
|
const textRules = [requiredField]
|
||||||
|
const minPowerRules = [
|
||||||
|
requiredField,
|
||||||
|
(v: number) =>
|
||||||
|
v <= maxPower.value ? true : 'Minimum power must be less than or equal to maximum power.'
|
||||||
|
]
|
||||||
|
const maxPowerRules = [
|
||||||
|
requiredField,
|
||||||
|
(v: number) =>
|
||||||
|
v >= minPower.value ? true : 'Maximum power must be greater than or equal to minimum power.'
|
||||||
|
]
|
||||||
|
const listToProps = (item: EventItemListing) => ({ title: item.name })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-row justify="center">
|
||||||
|
<v-col lg="8" sm="12">
|
||||||
|
<v-card fluid>
|
||||||
|
<v-card-title>Add New Item</v-card-title>
|
||||||
|
<v-card-item>
|
||||||
|
<v-form @submit.prevent>
|
||||||
|
<v-sheet>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-select
|
||||||
|
:items="eventItems"
|
||||||
|
:item-props="listToProps"
|
||||||
|
label="Item Type"
|
||||||
|
></v-select>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-text-field
|
||||||
|
v-model="itemName"
|
||||||
|
:rules="textRules"
|
||||||
|
label="Item Name"
|
||||||
|
></v-text-field>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-text-field
|
||||||
|
v-model="iconURL"
|
||||||
|
:rules="textRules"
|
||||||
|
label="Image URL"
|
||||||
|
disabled
|
||||||
|
></v-text-field>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col class="pl-0">
|
||||||
|
<v-text-field
|
||||||
|
v-model="minPower"
|
||||||
|
:rules="minPowerRules"
|
||||||
|
label="Minimum Power"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col class="pr-0">
|
||||||
|
<v-text-field
|
||||||
|
v-model="maxPower"
|
||||||
|
:rules="maxPowerRules"
|
||||||
|
label="Maximum Power"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-select :items="rarities" :item-props="listToProps" label="Rarity"></v-select>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-text-field v-model="origin" :rules="textRules" label="Origin"></v-text-field>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-text-field
|
||||||
|
v-model="tooltip"
|
||||||
|
:rules="textRules"
|
||||||
|
label="Tooltip"
|
||||||
|
></v-text-field>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-select
|
||||||
|
:items="yesNo"
|
||||||
|
:item-props="listToProps"
|
||||||
|
label="Is From An Event?"
|
||||||
|
></v-select>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-sheet>
|
||||||
|
<v-btn type="submit" block>Submit</v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
@ -1,72 +1,82 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePowerItems } from '@/stores/powerItems'
|
import { usePowerItems } from '@/stores/powerItems'
|
||||||
import type { PowerItem } from '@/types/PowerItem';
|
import type { DataTableHeader } from '@/types/DataTableHeader'
|
||||||
|
import type { PowerItem } from '@/types/PowerItem'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
import type { VDataTable } from 'vuetify/components'
|
||||||
|
|
||||||
type SortItem = { key: string, order?: boolean | 'asc' | 'desc' }
|
type ReadonlyHeaders = VDataTable['headers']
|
||||||
|
type SortItem = { key: string; order?: boolean | 'asc' | 'desc' }
|
||||||
|
const { lgAndUp } = useDisplay()
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
items: Map<string, PowerItem>
|
items: Map<string, PowerItem>
|
||||||
minimumTotal: number
|
minimumTotal: number
|
||||||
maximumTotal: number
|
maximumTotal: number
|
||||||
averageTotal: number
|
averageTotal: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
items: () => new Map<string, PowerItem>
|
items: () => new Map<string, PowerItem>()
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers = ref([
|
const computedHeaders = computed((): ReadonlyHeaders => {
|
||||||
{
|
let initialHeaders: DataTableHeader[] = [
|
||||||
title: 'Event',
|
{
|
||||||
align: ' d-none d-lg-table-cell start',
|
title: 'Name',
|
||||||
sortable: true,
|
align: 'start',
|
||||||
value: '1.origin'
|
sortable: true,
|
||||||
},
|
value: '1.itemName'
|
||||||
{
|
},
|
||||||
title: 'Name',
|
{
|
||||||
align: 'start',
|
title: 'Owned',
|
||||||
sortable: true,
|
align: 'end',
|
||||||
value: '1.itemName'
|
sortable: true,
|
||||||
},
|
key: `1.owned`
|
||||||
{
|
},
|
||||||
title: 'Min.',
|
{
|
||||||
align: 'end d-none d-lg-table-cell',
|
title: 'Mean Total',
|
||||||
sortable: true,
|
align: 'end',
|
||||||
key: '1.minItemPower'
|
sortable: false,
|
||||||
},
|
key: `1.aveTotalPower`
|
||||||
{
|
}
|
||||||
title: 'Max.',
|
]
|
||||||
align: 'end d-none d-lg-table-cell',
|
if (lgAndUp.value) {
|
||||||
sortable: true,
|
initialHeaders!.unshift({
|
||||||
key: '1.maxItemPower'
|
title: 'Event',
|
||||||
},
|
align: 'start',
|
||||||
{
|
sortable: true,
|
||||||
title: 'Owned',
|
value: '1.origin'
|
||||||
align: 'end',
|
})
|
||||||
sortable: true,
|
initialHeaders.splice(2, 0, {
|
||||||
key: `1.owned`
|
title: 'Min.',
|
||||||
},
|
align: 'end',
|
||||||
{
|
sortable: true,
|
||||||
title: 'Min. Total',
|
key: '1.minItemPower'
|
||||||
align: 'end d-none d-lg-table-cell',
|
})
|
||||||
sortable: false,
|
initialHeaders.splice(3, 0, {
|
||||||
key: `1.minTotalPower`
|
title: 'Max.',
|
||||||
},
|
align: 'end',
|
||||||
{
|
sortable: true,
|
||||||
title: 'Max. Total',
|
key: '1.maxItemPower'
|
||||||
align: 'end d-none d-lg-table-cell',
|
})
|
||||||
sortable: false,
|
initialHeaders.splice(5, 0, {
|
||||||
key: `1.maxTotalPower`
|
title: 'Min. Total',
|
||||||
},
|
align: 'end',
|
||||||
{
|
sortable: false,
|
||||||
title: 'Mean Total',
|
key: `1.minTotalPower`
|
||||||
align: 'end',
|
})
|
||||||
sortable: false,
|
initialHeaders.splice(6, 0, {
|
||||||
key: `1.aveTotalPower`
|
title: 'Max. Total',
|
||||||
|
align: 'end',
|
||||||
|
sortable: false,
|
||||||
|
key: `1.maxTotalPower`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
])
|
return initialHeaders as ReadonlyHeaders
|
||||||
|
})
|
||||||
const sortBy: Ref<SortItem[]> = ref([
|
const sortBy: Ref<SortItem[]> = ref([
|
||||||
{
|
{
|
||||||
key: '1.minItemPower',
|
key: '1.minItemPower',
|
||||||
|
|
@ -101,7 +111,7 @@ const filteredItems = computed(() =>
|
||||||
? value
|
? value
|
||||||
: undefined
|
: undefined
|
||||||
)
|
)
|
||||||
.filter((value) => value !== undefined)
|
.filter((value): value is [string, PowerItem] => !!value)
|
||||||
)
|
)
|
||||||
const getColor = computed(() => (rarity: number): string => {
|
const getColor = computed(() => (rarity: number): string => {
|
||||||
if (rarity === 5) {
|
if (rarity === 5) {
|
||||||
|
|
@ -151,9 +161,9 @@ toggle()
|
||||||
density="compact"
|
density="compact"
|
||||||
v-model:sort-by="sortBy"
|
v-model:sort-by="sortBy"
|
||||||
:items="filteredItems"
|
:items="filteredItems"
|
||||||
:headers="headers"
|
:headers="computedHeaders"
|
||||||
>
|
>
|
||||||
<template v-slot:item.1.origin="{ item }">
|
<template v-slot:[`item.1.origin`]="{ item }">
|
||||||
<v-card
|
<v-card
|
||||||
class="my-2"
|
class="my-2"
|
||||||
elevation="2"
|
elevation="2"
|
||||||
|
|
@ -163,10 +173,10 @@ toggle()
|
||||||
color="transparent"
|
color="transparent"
|
||||||
>
|
>
|
||||||
<v-img :src="getEventImageUrl(item[1].origin)"></v-img>
|
<v-img :src="getEventImageUrl(item[1].origin)"></v-img>
|
||||||
<v-tooltip activator="parent">{{ item?.[1].origin }}</v-tooltip>
|
<v-tooltip activator="parent">{{ item[1].origin }}</v-tooltip> </v-card
|
||||||
</v-card>
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.itemName="{ item }">
|
<template v-slot:[`item.1.itemName`]="{ item }">
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
activator="parent"
|
activator="parent"
|
||||||
v-if="item[1].tooltip != undefined"
|
v-if="item[1].tooltip != undefined"
|
||||||
|
|
@ -183,7 +193,7 @@ toggle()
|
||||||
{{ item[1].itemName }}
|
{{ item[1].itemName }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.owned="{ item }">
|
<template v-slot:[`item.1.owned`]="{ item }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
density="compact"
|
density="compact"
|
||||||
hide-details="auto"
|
hide-details="auto"
|
||||||
|
|
@ -191,13 +201,13 @@ toggle()
|
||||||
@update:model-value="usePowerItems().updateOwned(item[0], item[1].owned)"
|
@update:model-value="usePowerItems().updateOwned(item[0], item[1].owned)"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.minTotalPower="{ item }">
|
<template v-slot:[`item.1.minTotalPower`]="{ item }">
|
||||||
{{ item[1].minItemPower * item[1].owned }}
|
{{ item[1].minItemPower * item[1].owned }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.maxTotalPower="{ item }">
|
<template v-slot:[`item.1.maxTotalPower`]="{ item }">
|
||||||
{{ item[1].maxItemPower * item[1].owned }}
|
{{ item[1].maxItemPower * item[1].owned }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.aveTotalPower="{ item }">
|
<template v-slot:[`item.1.aveTotalPower`]="{ item }">
|
||||||
{{ ((item[1].minItemPower + item[1].maxItemPower) / 2) * item[1].owned }}
|
{{ ((item[1].minItemPower + item[1].maxItemPower) / 2) * item[1].owned }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:tfoot>
|
<template v-slot:tfoot>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePowerItems } from '@/stores/powerItems'
|
import { usePowerItems } from '@/stores/powerItems'
|
||||||
import type { PowerItem } from '@/types/PowerItem'
|
import type { PowerItem } from '@/types/PowerItem'
|
||||||
|
import type { Ref } from 'vue';
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useDisplay } from 'vuetify';
|
||||||
|
import type { VDataTable } from 'vuetify/components';
|
||||||
|
|
||||||
|
type ReadonlyHeaders = VDataTable['headers']
|
||||||
|
type SortItem = { key: string; order?: boolean | 'asc' | 'desc' }
|
||||||
|
const { lgAndUp } = useDisplay()
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
items: Map<string, PowerItem>
|
items: Map<string, PowerItem>
|
||||||
|
|
@ -12,39 +19,46 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
items: () => new Map<string, PowerItem>()
|
items: () => new Map<string, PowerItem>()
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers = ref([
|
const headers = computed((): ReadonlyHeaders => {
|
||||||
{
|
const initialHeaders = [
|
||||||
title: '',
|
{
|
||||||
align: 'start',
|
title: '',
|
||||||
sortable: false,
|
align: 'start',
|
||||||
value: '1.iconURL'
|
sortable: false,
|
||||||
},
|
value: '1.iconURL'
|
||||||
{
|
},
|
||||||
title: 'Name',
|
{
|
||||||
align: ' d-none d-lg-table-cell start',
|
title: 'Owned',
|
||||||
sortable: true,
|
align: 'end',
|
||||||
value: '1.itemName'
|
sortable: true,
|
||||||
},
|
key: `1.owned`
|
||||||
{
|
},
|
||||||
title: 'Power',
|
{
|
||||||
align: ' d-none d-lg-table-cell',
|
title: 'Total',
|
||||||
sortable: true,
|
align: 'end',
|
||||||
key: '1.minItemPower'
|
sortable: false,
|
||||||
},
|
key: `1.totalPower`
|
||||||
{
|
}
|
||||||
title: 'Owned',
|
]
|
||||||
align: 'end',
|
if (lgAndUp.value) {
|
||||||
sortable: true,
|
initialHeaders!.splice(1, 0,
|
||||||
key: `1.owned`
|
{
|
||||||
},
|
title: 'Name',
|
||||||
{
|
align: 'start',
|
||||||
title: 'Total',
|
sortable: true,
|
||||||
align: 'end',
|
value: '1.itemName'
|
||||||
sortable: false,
|
})
|
||||||
key: `1.totalPower`
|
initialHeaders!.splice(2, 0,
|
||||||
|
{
|
||||||
|
title: 'Power',
|
||||||
|
align: 'end',
|
||||||
|
sortable: true,
|
||||||
|
key: '1.minItemPower'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
])
|
return initialHeaders as ReadonlyHeaders
|
||||||
const sortBy = ref([
|
})
|
||||||
|
const sortBy: Ref<SortItem[]> = ref([
|
||||||
{
|
{
|
||||||
key: '1.minItemPower',
|
key: '1.minItemPower',
|
||||||
order: 'asc'
|
order: 'asc'
|
||||||
|
|
@ -77,7 +91,7 @@ const getColor = computed(() => (rarity: number): string => {
|
||||||
:items="[...items.entries()]"
|
:items="[...items.entries()]"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
>
|
>
|
||||||
<template v-slot:item.1.iconURL="{ item }">
|
<template v-slot:[`item.1.iconURL`]="{ item }">
|
||||||
<v-card class="my-2" elevation="2" rounded width="41">
|
<v-card class="my-2" elevation="2" rounded width="41">
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
activator="parent"
|
activator="parent"
|
||||||
|
|
@ -94,7 +108,7 @@ const getColor = computed(() => (rarity: number): string => {
|
||||||
<v-img :src="`/images/${item[1].iconURL}`" height="41" width="41" cover></v-img>
|
<v-img :src="`/images/${item[1].iconURL}`" height="41" width="41" cover></v-img>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.itemName="{ item }">
|
<template v-slot:[`item.1.itemName`]="{ item }">
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
activator="parent"
|
activator="parent"
|
||||||
v-if="item[1].tooltip != undefined"
|
v-if="item[1].tooltip != undefined"
|
||||||
|
|
@ -111,7 +125,7 @@ const getColor = computed(() => (rarity: number): string => {
|
||||||
{{ item[1].itemName }}
|
{{ item[1].itemName }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.owned="{ item }">
|
<template v-slot:[`item.1.owned`]="{ item }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
density="compact"
|
density="compact"
|
||||||
hide-details="auto"
|
hide-details="auto"
|
||||||
|
|
@ -119,7 +133,7 @@ const getColor = computed(() => (rarity: number): string => {
|
||||||
@update:model-value="usePowerItems().updateOwned(item[0], item[1].owned)"
|
@update:model-value="usePowerItems().updateOwned(item[0], item[1].owned)"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.1.totalPower="{ item }">
|
<template v-slot:[`item.1.totalPower`]="{ item }">
|
||||||
{{ item[1].minItemPower * item[1].owned }}
|
{{ item[1].minItemPower * item[1].owned }}
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:tfoot>
|
<template v-slot:tfoot>
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,11 @@ import router from './router'
|
||||||
import 'vuetify/styles'
|
import 'vuetify/styles'
|
||||||
import '@mdi/font/css/materialdesignicons.css'
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
import { createVuetify } from 'vuetify'
|
import { createVuetify } from 'vuetify'
|
||||||
import * as components from 'vuetify/components'
|
|
||||||
import * as directives from 'vuetify/directives'
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import zitadelAuth from '@/services/zitadelAuth'
|
import zitadelAuth from '@/services/zitadelAuth'
|
||||||
import { apiBaseURL } from './types/ConfigSymbols'
|
import { apiBaseURL } from './types/ConfigSymbols'
|
||||||
|
|
||||||
const vuetify = createVuetify({
|
const vuetify = createVuetify({
|
||||||
components,
|
|
||||||
directives,
|
|
||||||
icons: {
|
icons: {
|
||||||
defaultSet: 'mdi'
|
defaultSet: 'mdi'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/test',
|
path: '/test',
|
||||||
name: 'test',
|
name: 'test',
|
||||||
component: () => import('@/views/Test.vue')
|
component: () => import('@/views/TestView.vue')
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
110
src/types/DataTableHeader.ts
Normal file
110
src/types/DataTableHeader.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import type { ComputedRef } from "vue"
|
||||||
|
import type { Ref } from "vue"
|
||||||
|
|
||||||
|
export type DataTableHeader = {
|
||||||
|
key?: 'data-table-group' | 'data-table-select' | 'data-table-expand' | (string & {})
|
||||||
|
value?: SelectItemKey
|
||||||
|
title?: string
|
||||||
|
fixed?: boolean
|
||||||
|
align?: 'start' | 'end' | 'center'
|
||||||
|
width?: number | string
|
||||||
|
minWidth?: string
|
||||||
|
maxWidth?: string
|
||||||
|
headerProps?: Record<string, any>
|
||||||
|
cellProps?: HeaderCellProps
|
||||||
|
sortable?: boolean
|
||||||
|
sort?: DataTableCompareFunction
|
||||||
|
filter?: FilterFunction
|
||||||
|
children?: DataTableHeader[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelectItemKey<T = Record<string, any>> = boolean | null | undefined | string | readonly (string | number)[] | ((item: T, fallback?: any) => any);
|
||||||
|
type HeaderCellProps = Record<string, any> | ((data: Pick<ItemKeySlot<any>, 'index' | 'item' | 'internalItem' | 'value'>) => Record<string, any>);
|
||||||
|
type DataTableCompareFunction<T = any> = (a: T, b: T) => number;
|
||||||
|
type FilterFunction = (value: string, query: string, item?: InternalItem) => FilterMatch;
|
||||||
|
type ItemKeySlot<T> = ItemSlotBase<T> & {
|
||||||
|
value: any;
|
||||||
|
column: InternalDataTableHeader;
|
||||||
|
};
|
||||||
|
type FilterMatch = boolean | number | [number, number] | [number, number][];
|
||||||
|
type ItemSlotBase<T> = {
|
||||||
|
index: number;
|
||||||
|
item: T;
|
||||||
|
internalItem: DataTableItem<T>;
|
||||||
|
isExpanded: ReturnType<typeof provideExpanded>['isExpanded'];
|
||||||
|
toggleExpand: ReturnType<typeof provideExpanded>['toggleExpand'];
|
||||||
|
isSelected: ReturnType<typeof provideSelection>['isSelected'];
|
||||||
|
toggleSelect: ReturnType<typeof provideSelection>['toggleSelect'];
|
||||||
|
};
|
||||||
|
type InternalDataTableHeader = Omit<DataTableHeader, 'key' | 'value' | 'children'> & {
|
||||||
|
key: string | null;
|
||||||
|
value: SelectItemKey | null;
|
||||||
|
sortable: boolean;
|
||||||
|
fixedOffset?: number;
|
||||||
|
lastFixed?: boolean;
|
||||||
|
colspan?: number;
|
||||||
|
rowspan?: number;
|
||||||
|
children?: InternalDataTableHeader[];
|
||||||
|
};
|
||||||
|
type ExpandProps = {
|
||||||
|
expandOnClick: boolean;
|
||||||
|
expanded: readonly string[];
|
||||||
|
'onUpdate:expanded': ((value: any[]) => void) | undefined;
|
||||||
|
};
|
||||||
|
type SelectionProps = Pick<DataTableItemProps, 'itemValue'> & {
|
||||||
|
modelValue: readonly any[];
|
||||||
|
selectStrategy: 'single' | 'page' | 'all';
|
||||||
|
valueComparator: typeof deepEqual;
|
||||||
|
'onUpdate:modelValue': EventProp<[any[]]> | undefined;
|
||||||
|
};
|
||||||
|
type EventProp<T extends any[] = any[], F = (...args: T) => void> = F;
|
||||||
|
|
||||||
|
declare function provideExpanded(props: ExpandProps): {
|
||||||
|
expand: (item: DataTableItem, value: boolean) => void;
|
||||||
|
expanded: Ref<Set<string>> & {
|
||||||
|
readonly externalValue: readonly string[];
|
||||||
|
};
|
||||||
|
expandOnClick: Ref<boolean>;
|
||||||
|
isExpanded: (item: DataTableItem) => boolean;
|
||||||
|
toggleExpand: (item: DataTableItem) => void;
|
||||||
|
};
|
||||||
|
declare function provideSelection(props: SelectionProps, { allItems, currentPage }: {
|
||||||
|
allItems: Ref<SelectableItem[]>;
|
||||||
|
currentPage: Ref<SelectableItem[]>;
|
||||||
|
}): {
|
||||||
|
toggleSelect: (item: SelectableItem) => void;
|
||||||
|
select: (items: SelectableItem[], value: boolean) => void;
|
||||||
|
selectAll: (value: boolean) => void;
|
||||||
|
isSelected: (items: SelectableItem | SelectableItem[]) => boolean;
|
||||||
|
isSomeSelected: (items: SelectableItem | SelectableItem[]) => boolean;
|
||||||
|
someSelected: ComputedRef<boolean>;
|
||||||
|
allSelected: ComputedRef<boolean>;
|
||||||
|
showSelectAll: boolean;
|
||||||
|
};
|
||||||
|
declare function deepEqual(a: any, b: any): boolean;
|
||||||
|
|
||||||
|
interface InternalItem<T = any> {
|
||||||
|
value: any;
|
||||||
|
raw: T;
|
||||||
|
}
|
||||||
|
interface DataTableItemProps {
|
||||||
|
items: any[];
|
||||||
|
itemValue: SelectItemKey;
|
||||||
|
itemSelectable: SelectItemKey;
|
||||||
|
returnObject: boolean;
|
||||||
|
}
|
||||||
|
interface DataTableItem<T = any> extends InternalItem<T>, GroupableItem<T>, SelectableItem {
|
||||||
|
key: any;
|
||||||
|
index: number;
|
||||||
|
columns: {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface SelectableItem {
|
||||||
|
value: any;
|
||||||
|
selectable: boolean;
|
||||||
|
}
|
||||||
|
interface GroupableItem<T = any> {
|
||||||
|
type: 'item';
|
||||||
|
raw: T;
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ const {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card class="flex-fill">
|
||||||
<v-card-title>Blessing Power</v-card-title>
|
<v-card-title>Blessing Power</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-sheet class="d-flex flex-wrap flex-fill">
|
<v-sheet class="d-flex flex-wrap flex-fill">
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card class="flex-fill">
|
||||||
<v-card-title>Fellow Power</v-card-title>
|
<v-card-title>Fellow Power</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-sheet class="d-flex flex-wrap flex-fill">
|
<v-sheet class="d-flex flex-wrap flex-fill">
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card class="flex-fill">
|
||||||
<v-card-title>Intimacy Power</v-card-title>
|
<v-card-title>Intimacy Power</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-sheet class="d-flex flex-wrap flex-fill">
|
<v-sheet class="d-flex flex-wrap flex-fill">
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SpecialItemsCard from '@/components/SpecialItemsCard.vue'
|
|
||||||
import StandardItemsCard from '@/components/StandardItemsCard.vue'
|
|
||||||
import SummaryCard from '@/components/SummaryCard.vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
|
||||||
import { usePowerItems } from '@/stores/powerItems'
|
|
||||||
|
|
||||||
const {
|
|
||||||
standardBlessingItems,
|
|
||||||
standardBlessingItemTotal,
|
|
||||||
specialBlessingItems,
|
|
||||||
specialBlessingItemsMinTotal,
|
|
||||||
specialBlessingItemsMaxTotal,
|
|
||||||
specialBlessingItemsAveTotal
|
|
||||||
} = storeToRefs(usePowerItems())
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title>Intimacy Power</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-sheet class="d-flex flex-wrap flex-fill">
|
|
||||||
<StandardItemsCard
|
|
||||||
class="ma-2 align-self-start"
|
|
||||||
:items="standardBlessingItems"
|
|
||||||
:total="standardBlessingItemTotal"
|
|
||||||
/>
|
|
||||||
<SpecialItemsCard
|
|
||||||
class="ma-2 align-self-start"
|
|
||||||
:items="specialBlessingItems"
|
|
||||||
:minimum-total="specialBlessingItemsMinTotal"
|
|
||||||
:maximum-total="specialBlessingItemsMaxTotal"
|
|
||||||
:average-total="specialBlessingItemsAveTotal"
|
|
||||||
/>
|
|
||||||
<SummaryCard
|
|
||||||
class="ma-2 align-self-start"
|
|
||||||
:standard-total="standardBlessingItemTotal"
|
|
||||||
:minimum-total="specialBlessingItemsMinTotal"
|
|
||||||
:maximum-total="specialBlessingItemsMaxTotal"
|
|
||||||
:average-total="specialBlessingItemsAveTotal"
|
|
||||||
/>
|
|
||||||
</v-sheet>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@use '@/styles/settings.scss';
|
|
||||||
:deep(tbody) tr:nth-of-type(even) {
|
|
||||||
background-color: rgba(var(--v-theme-primary-darken-1), 0.25);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
13
src/views/TestView.vue
Normal file
13
src/views/TestView.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NewItemForm from '@/components/NewItemForm.vue';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-sheet class="flex-fill">
|
||||||
|
<NewItemForm />
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
|
|
@ -2,11 +2,13 @@ import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vuetify from 'vite-plugin-vuetify'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
|
vuetify(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue