Aktualizacja do wersji 0.0.28 i poprawa formatowania raportów

- Zmieniono wersję z 0.0.27 na 0.0.28 w `package.json`, `dockerfile` i interfejsie użytkownika.
- Dodano `useFormatNumber` i `useRedisKeyPrefix` do formatowania liczb oraz generowania prefixów do kluczy redis.
- Zaktualizowano auto-importy ESLint i deklaracje typów dla nowych composables.
- Poprawiono formatowanie raportu administracyjnego poprzez lepsze wiązanie danych i formatowanie wartości.
- Dostosowano `AdministrationAccordion`, aby domyślnie rozwijał pierwszą sekcję.
- Zmieniono komponenty wykresów i podpowiedzi na `useFormatNumber` dla spójnego formatowania.
- Usprawniono obliczenia w `populationGrowth`, eliminując zbędne funkcje formatowania.
- Zoptymalizowano klucze pamięci podręcznej dzięki `redisKeyPrefix` dla lepszej wydajności.
- Zaktualizowano `.gitignore`, dodając katalog `auto-types`.
This commit is contained in:
2025-02-02 23:27:27 +01:00
parent e65fbd92a0
commit d465642f72
19 changed files with 238 additions and 57 deletions

View File

@@ -508,6 +508,8 @@
"PaginationNext": true,
"PaginationPrev": true,
"useFetchPopulationReport": true,
"useFetchAdministrationReport": true
"useFetchAdministrationReport": true,
"useRedisKeyPrefix": true,
"useFormatNumber": true
}
}

4
.gitignore vendored
View File

@@ -42,4 +42,6 @@ slim.report.json
redis/data
commit.txt
git-diff.txt
git-diff.txt
auto-types

View File

@@ -14,7 +14,7 @@ RUN bun run build-only \
FROM alpine:latest
LABEL maintainer="garandplg@garandplg.com"
LABEL version="0.0.27"
LABEL version="0.0.28"
LABEL description="Garand's WG. Pocketbase + Redis + Bun + Vue3 + Vite + TypeScript + TailwindCSS + Shadcn-vue"
ARG PB_VERSION=0.22.26

View File

@@ -1,6 +1,6 @@
{
"name": "garands-world-game",
"version": "0.0.27",
"version": "0.0.28",
"private": true,
"type": "module",
"scripts": {

View File

@@ -340,6 +340,7 @@ declare global {
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFormField: typeof import('../components/ui/form/useFormField')['useFormField']
const useFormatNumber: typeof import('../composables/useFormatNumber')['default']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
@@ -394,6 +395,7 @@ declare global {
const useProcessEndTurnCollectionsData: typeof import('../composables/useProcessEndTurnCollectionsData')['default']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRedis: typeof import('../composables/useRedis')['useRedis']
const useRedisKeyPrefix: typeof import('../composables/useRedisKeyPrefix')['default']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
@@ -822,6 +824,7 @@ declare module 'vue' {
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFormField: UnwrapRef<typeof import('../components/ui/form/useFormField')['useFormField']>
readonly useFormatNumber: UnwrapRef<typeof import('../composables/useFormatNumber')['default']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
@@ -876,6 +879,7 @@ declare module 'vue' {
readonly useProcessEndTurnCollectionsData: UnwrapRef<typeof import('../composables/useProcessEndTurnCollectionsData')['default']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRedis: UnwrapRef<typeof import('../composables/useRedis')['useRedis']>
readonly useRedisKeyPrefix: UnwrapRef<typeof import('../composables/useRedisKeyPrefix')['default']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>

View File

@@ -13,6 +13,7 @@ declare module 'vue' {
AccordionTrigger: typeof import('./../components/ui/accordion/AccordionTrigger.vue')['default']
AdministrationAccordion: typeof import('./../components/report/administration/administrationAccordion.vue')['default']
AdministrationBurgCard: typeof import('./../components/report/administration/administrationBurgCard.vue')['default']
AdministrationDetails: typeof import('./../components/report/administration/administrationDetails.vue')['default']
AdministrationProvincesCard: typeof import('./../components/report/administration/administrationProvincesCard.vue')['default']
AdministrationReport: typeof import('./../components/report/administrationReport.vue')['default']
BuildingsReport: typeof import('./../components/report/buildingsReport.vue')['default']

View File

@@ -85,11 +85,7 @@ const changePage = (page: number) => {
class="mb-3 mt-6 h-[600px]"
:show-legend="false"
:custom-tooltip="customChartTooltip"
:y-formatter="
(tick, i) => {
return `${tick.toLocaleString('en-US')}`;
}
"
:y-formatter="(tick, i) => useFormatNumber(tick)"
/>
</div>
</template>

View File

@@ -31,7 +31,7 @@ defineProps<{
</span>
<span>{{ item.name }}</span>
</div>
<span class="ml-4 font-semibold">{{ item.value.toLocaleString('en-US') }}</span>
<span class="ml-4 font-semibold">{{ useFormatNumber(item.value) }}</span>
</div>
</CardContent>
</Card>

View File

@@ -16,12 +16,15 @@ const props = defineProps<{
const provinces = props.accordionItems.find(item => item.type === 'provinces')?.content ?? [];
const burgs = props.accordionItems.find(item => item.type === 'burgs')?.content ?? [];
const { cultureNames, religionNames } = await useFetchAdministrationReport(props.turn, props.state);
</script>
<template>
<Accordion type="single" class="w-full" collapsible>
<Accordion
type="single"
class="w-full p-2"
collapsible
:default-value="props.accordionItems[0].title"
>
<AccordionItem v-for="item in props.accordionItems" :key="item.title" :value="item.title">
<AccordionTrigger class="items-center justify-center space-x-1">
<h3 class="text-lg font-bold text-primary"> {{ item.title }} </h3>
@@ -48,8 +51,8 @@ const { cultureNames, religionNames } = await useFetchAdministrationReport(props
v-for="burg in burgs"
:key="burg.id"
:burg="burg"
:cultures="cultureNames"
:religions="religionNames"
:turn="props.turn"
:state="props.state"
/>
</div>
</ScrollArea>
@@ -63,6 +66,7 @@ const { cultureNames, religionNames } = await useFetchAdministrationReport(props
:data="item.content"
type="donut"
:sort-function="(a, b) => b.amount - a.amount"
:valueFormatter="value => `${useFormatNumber(value)}`"
/>
</div>
</AccordionContent>

View File

@@ -1,12 +1,14 @@
<script lang="ts" setup>
import type { BurgsResponse, CulturesResponse, ReligionsResponse } from '@/types/pb-types';
import type { BurgsResponse, StatesResponse, TurnsResponse } from '@/types/pb-types';
import { Icon } from '@iconify/vue';
const props = defineProps<{
burg: BurgsResponse<unknown>;
cultures: CulturesResponse<unknown>[];
religions: ReligionsResponse<unknown>[];
turn: TurnsResponse<unknown>;
state: StatesResponse<unknown, unknown, unknown, unknown, unknown>;
}>();
const { cultureNames, religionNames } = await useFetchAdministrationReport(props.turn, props.state);
</script>
<template>
@@ -25,20 +27,20 @@ const props = defineProps<{
<div class="mr-3 mt-[-0.6rem] flex w-full flex-col">
<p class="text-base">
Populacja:
<span class="text-primary">{{ props.burg.population.toLocaleString('en-US') }}</span>
<span class="text-primary">{{ useFormatNumber(props.burg.population) }}</span>
</p>
<p class="text-base">
Kultura:
<span class="text-primary">
{{ props.cultures.find(culture => culture.id === props.burg.culture)?.name }}
{{ cultureNames.find(culture => culture.id === props.burg.culture)?.name }}
</span>
</p>
<p class="text-base">
Religia:
<span class="text-primary">
{{ props.religions.find(religion => religion.id === props.burg.religion)?.name }}
{{ religionNames.find(religion => religion.id === props.burg.religion)?.name }}
</span>
</p>

View File

@@ -0,0 +1,157 @@
<script lang="ts" setup>
import type { StatesResponse, TurnsResponse } from '@/types/pb-types';
const props = defineProps<{
turn: TurnsResponse<unknown>;
state: StatesResponse<unknown, unknown, unknown, unknown, unknown>;
}>();
const { cultureNames, burgsData } = await useFetchAdministrationReport(props.turn, props.state);
const cells = props.state.cells;
const primaryCulture = cultureNames.find(
(cultureName: { id: string; name: string }) => cultureName.id === props.state.culture
).name;
const cultureCells = props.state.culturesCells as Record<string, number>;
const nonPrimaryCultureCells = Object.entries(cultureCells).reduce((suma, [klucz, wartość]) => {
return klucz === primaryCulture ? suma : suma + wartość;
}, 0);
const burgs = burgsData.value.length;
const nonPrimaryCultureBurgs = burgsData.value.filter(
burg => burg.culture === primaryCulture
).length;
const sumGovernmentCapacityPoints = cells + nonPrimaryCultureCells + burgs + nonPrimaryCultureBurgs;
const progress = ref(33);
// TODO: Dodać limit administracyjny, wyliczanie % zapełnienia limitu i jego wypisywanie w <Progress />>
</script>
<template>
<div class="w-full p-6">
<Label class="mt-3 text-lg" for="progress">
Wykorzystanie: 33 / 100 ( <span class="text-primary"> 33</span>% )
</Label>
<Progress v-model="progress" class="mt-1 w-3/5" id="progress" />
<div class="mt-9 grid grid-cols-2 gap-10">
<div class="relative mt-2 h-auto w-full rounded-md border p-5">
<span class="absolute -top-4 left-2 bg-background px-2 text-xl text-primary">
Koszty administracyjne
</span>
<Table class="mb-7">
<TableHeader>
<TableRow>
<TableHead> Typ </TableHead>
<TableHead> Punkty </TableHead>
<TableHead class="text-right"> Ilość </TableHead>
</TableRow>
</TableHeader>
<TableBody class="font-medium">
<TableRow>
<TableCell> Kratki </TableCell>
<TableCell> 1 </TableCell>
<TableCell class="text-right"> {{ useFormatNumber(cells) }} </TableCell>
</TableRow>
<TableRow>
<TableCell> Kratki ob. kultura </TableCell>
<TableCell> 1 </TableCell>
<TableCell class="text-right">
{{ useFormatNumber(nonPrimaryCultureCells) }}
</TableCell>
</TableRow>
<TableRow>
<TableCell> Miasta </TableCell>
<TableCell> 1 </TableCell>
<TableCell class="text-right"> {{ useFormatNumber(burgs) }} </TableCell>
</TableRow>
<TableRow>
<TableCell> Miasta ob. kultura </TableCell>
<TableCell> 1 </TableCell>
<TableCell class="text-right">
{{ useFormatNumber(nonPrimaryCultureBurgs) }}
</TableCell>
</TableRow>
</TableBody>
</Table>
<p class="absolute bottom-3 start-1/3 mt-4 text-center text-lg">
Suma:
<span class="text-primary"> {{ useFormatNumber(sumGovernmentCapacityPoints) }} </span>
</p>
</div>
<div class="relative mt-2 h-auto w-full rounded-md border p-5">
<span class="absolute -top-4 left-2 bg-background px-2 text-xl text-primary">
Źródła pojemności
</span>
<Table class="mb-7">
<TableHeader>
<TableRow>
<TableHead> Typ </TableHead>
<TableHead class="text-right"> Ilość </TableHead>
</TableRow>
</TableHeader>
<TableBody class="font-medium">
<TableRow>
<TableCell> Forma rządu </TableCell>
<TableCell class="text-right"> 0 </TableCell>
</TableRow>
<TableRow>
<TableCell> Technologia </TableCell>
<TableCell class="text-right"> 0 </TableCell>
</TableRow>
</TableBody>
</Table>
<p class="absolute bottom-3 start-1/3 mt-4 text-center text-lg">
Suma: <span class="text-primary"> 0 </span>
</p>
</div>
</div>
</div>
</template>
<!--
Pojemność Administracyjna
Wykorzystanie: 387 / 500 ( 77% )
[]
Koszty administracyjne Źródła pojemności
Kratki: 250 Forma rządu: 300
Kratki ob. kult: 50 Technologia: 200
Miasta: 75
Miasta ob. kult: 12
Suma: 387 Suma: 500
-->

View File

@@ -5,7 +5,7 @@ const props = defineProps<{ province: ProvincesResponse<unknown> }>();
</script>
<template>
<div class="flex h-[165px] w-[500px] flex-col gap-2 rounded-md border">
<div class="flex h-[165px] w-[450px] flex-col gap-2 rounded-md border">
<div class="flex items-center justify-center">
<span class="mr-2 h-3 w-3" v-if="props.province.color">
<svg width="100%" height="100%" viewBox="0 0 30 30">
@@ -36,26 +36,22 @@ const props = defineProps<{ province: ProvincesResponse<unknown> }>();
<p class="text-base">
Populacja wiejska:
<span class="text-primary">{{
props.province.ruralPopulation.toLocaleString('en-US')
}}</span>
<span class="text-primary">{{ useFormatNumber(props.province.ruralPopulation) }}</span>
</p>
<p class="text-base">
Populacja miejska:
<span class="text-primary">{{
props.province.urbanPopulation.toLocaleString('en-US')
}}</span>
<span class="text-primary">{{ useFormatNumber(props.province.urbanPopulation) }}</span>
</p>
<p class="text-base">
Ilość miast:
<span class="text-primary">{{ props.province.burgs.toLocaleString('en-US') }}</span>
<span class="text-primary">{{ useFormatNumber(props.province.burgs) }}</span>
</p>
<p class="text-base">
Ilość kratek:
<span class="text-primary">{{ props.province.cells.toLocaleString('en-US') }}</span>
<span class="text-primary">{{ useFormatNumber(props.province.cells) }}</span>
</p>
</div>
</div>

View File

@@ -68,14 +68,17 @@ function prepareChartData(content: object) {
<template>
<div class="grid grid-cols-12 gap-3">
<div class="col-span-4 rounded-md border p-2">
<AdministrationAccordion
:accordionItems="accordionItems"
:turn="props.turn"
:state="props.state"
/>
</div>
<AdministrationAccordion
class="col-span-6"
:accordionItems="accordionItems"
:turn="props.turn"
:state="props.state"
/>
<div class="col-span-8 rounded-md border p-2">Zawartość</div>
<AdministrationDetails
:state="props.state"
:turn="props.turn"
class="col-span-6 flex w-full flex-col items-center justify-center"
/>
</div>
</template>

View File

@@ -25,10 +25,10 @@ const props = defineProps<{
state: StatesResponse<unknown, unknown, unknown, unknown, unknown>;
}>();
const { currentUser } = storeToRefs(listenCurrentUserStore());
const { redisKeyPrefix } = useRedisKeyPrefix(props.state.name, props.turn.value);
const flags: Flag[] = await useCachedData({
key: `${props.state.name}-${props.turn.value + 1}-${!['moderator', 'admin'].includes(currentUser.value?.role) ? currentUser.value?.id : 'Administration'} -- reportDiplomacyFlags`,
key: `${redisKeyPrefix} -- reportDiplomacyFlags`,
collection: 'states',
ttl: 1800,
requestArgs: {

View File

@@ -25,8 +25,6 @@ const totalData = computed(() => ({
growth: ruralData.value.growth + urbanData.value.growth
}));
const formatNumber = (num: number) => num.toLocaleString('en-US');
const getChangeColor = (change: number) => {
if (change > 0) return 'text-green-500';
if (change < 0) return 'text-red-500';
@@ -41,7 +39,7 @@ const getChangeSymbol = (change: number) => {
const formatChange = (change: number) => {
const prefix = change > 0 ? '+' : '';
return `${prefix}${formatNumber(change)}`;
return `${prefix}${useFormatNumber(change)}`;
};
</script>
@@ -62,7 +60,7 @@ const formatChange = (change: number) => {
<div class="numbers-display">
<div class="text-2xl font-bold">
{{ formatNumber(ruralData.current) }}
{{ useFormatNumber(ruralData.current) }}
</div>
<div :class="getChangeColor(ruralData.change)">
@@ -71,7 +69,7 @@ const formatChange = (change: number) => {
</div>
<div class="text-xl">
{{ formatNumber(ruralData.growth) }}
{{ useFormatNumber(ruralData.growth) }}
</div>
</div>
</div>
@@ -83,7 +81,7 @@ const formatChange = (change: number) => {
<div class="numbers-display">
<div class="text-2xl font-bold">
{{ formatNumber(urbanData.current) }}
{{ useFormatNumber(urbanData.current) }}
</div>
<div :class="getChangeColor(urbanData.change)">
@@ -92,7 +90,7 @@ const formatChange = (change: number) => {
</div>
<div class="text-xl">
{{ formatNumber(urbanData.growth) }}
{{ useFormatNumber(urbanData.growth) }}
</div>
</div>
</div>
@@ -105,7 +103,7 @@ const formatChange = (change: number) => {
<div class="flex items-center justify-center space-x-4">
<span class="text-2xl font-bold">
{{ formatNumber(totalData.current) }}
{{ useFormatNumber(totalData.current) }}
</span>
<div class="flex flex-col items-center">
@@ -116,7 +114,7 @@ const formatChange = (change: number) => {
</div>
<span class="text-2xl">
{{ formatNumber(totalData.growth) }}
{{ useFormatNumber(totalData.growth) }}
</span>
</div>
</div>

View File

@@ -0,0 +1,3 @@
export default function useFormatNumber(num: number | Date) {
return num.toLocaleString('en-US');
}

View File

@@ -0,0 +1,11 @@
export default function useRedisKeyPrefix(state: string, turn: string) {
const { currentUser } = storeToRefs(listenCurrentUserStore());
const user = currentUser.value?.id ? `"${currentUser.value.id}"` : null;
const isUserNotAdmin = !['moderator', 'admin'].includes(currentUser.value?.role);
const redisKeyPrefix = `${state}-${turn + 1}-${isUserNotAdmin ? user : 'Administration'}`;
return { redisKeyPrefix };
}

View File

@@ -11,7 +11,7 @@ useHead({
Witaj na <span class="text-primary">Garandowym WG</span>!
</h1>
<p class="mb-4 text-sm">Wersja: <span class="text-primary">0.0.27 Indev</span></p>
<p class="mb-4 text-sm">Wersja: <span class="text-primary">0.0.28 Indev</span></p>
<a
href="https://discord.gg/vhBmFVpR3w"

View File

@@ -33,14 +33,16 @@ const { currentUser } = storeToRefs(listenCurrentUserStore());
const turn = computed<string>(() => (route.params as RouteParams).turn - 1);
const state = computed<string>(() => (route.params as RouteParams).state);
useHead({
title: `Garand's WG | Raport | Tura ${turn.value} | ${state.value}`
});
const user = computed<AuthModel | null>(() =>
currentUser.value?.id ? `"${currentUser.value.id}"` : null
);
const isUserNotAdmin = !['moderator', 'admin'].includes(currentUser.value?.role);
useHead({
title: `Garand's WG | Raport | Tura ${turn.value} | ${state.value}`
});
const { redisKeyPrefix } = useRedisKeyPrefix(state.value, turn.value);
const turnData = ref<TurnsResponse<unknown>>();
const stateData = ref<StatesResponse<unknown, unknown, unknown, unknown, unknown>>();
@@ -49,7 +51,7 @@ const cultureData = ref<CulturesResponse<unknown>>();
if (!user.value) router.push('/');
else {
turnData.value = await useCachedData({
key: `${state.value}-${turn.value + 1} -- reportTurn`,
key: `${redisKeyPrefix} -- reportTurn`,
collection: 'turns',
ttl: 1800,
func: 'getFirstListItem',
@@ -62,7 +64,7 @@ else {
if (!turnData.value) throw new Error('Nie znaleziono tury');
stateData.value = await useCachedData({
key: `${state.value}-${turn.value + 1}-${isUserNotAdmin ? user.value : 'Administration'} -- reportState`,
key: `${redisKeyPrefix} -- reportState`,
collection: 'states',
ttl: 1800,
func: 'getFirstListItem',
@@ -72,7 +74,7 @@ else {
}).value,
requestArgs: {
fields: [
'id,name,nameFull,emblemSVG,color,culture,relations,biomesCells,culturesCells,religionsCells,capital,ruralPopulation,urbanPopulation'
'id,name,nameFull,emblemSVG,cells,color,culture,relations,biomesCells,culturesCells,religionsCells,capital,ruralPopulation,urbanPopulation'
]
}
});
@@ -80,7 +82,7 @@ else {
if (!stateData.value) throw new Error('Nie znaleziono kraju');
cultureData.value = await useCachedData({
key: `${state.value}-${turn.value + 1}-${isUserNotAdmin ? user.value : 'Administration'} -- reportCultureTechnologyGroup`,
key: `${redisKeyPrefix} -- reportCultureTechnologyGroup`,
collection: 'cultures',
ttl: 1800,
func: 'getFirstListItem',