Aktualizacja do wersji 0.0.27 i refaktoryzacja raportów

- Zaktualizowano `.gitignore`, dodając `commit.txt` i `git-diff.txt`
- Podniesiono wersję z `0.0.26` do `0.0.27` w `dockerfile`, `package.json` i interfejsie użytkownika
- Refaktoryzacja `populationReport.vue` i `populationAccordion.vue`:
  - Ulepszono obsługę danych poprzez wykorzystanie composables
  - Przebudowano komponenty raportu populacji dla lepszej organizacji
- Zmodyfikowano klucze cache w `useFetchAdministrationReport.ts` i `[turn]/[state].vue`
  - Dodano nazwę stanu do kluczy dla lepszego rozróżnienia
  - Uwzględniono nowe pola w zapytaniu o stan raportu
- Drobne poprawki kodu w `chartLineWithPagination.vue`
This commit is contained in:
2025-02-02 18:05:33 +01:00
parent b1d9da8a51
commit e65fbd92a0
12 changed files with 240 additions and 107 deletions

5
.gitignore vendored
View File

@@ -39,4 +39,7 @@ slim.report.json
/pocketbase/pocketbase
redis/data
redis/data
commit.txt
git-diff.txt

View File

@@ -14,7 +14,7 @@ RUN bun run build-only \
FROM alpine:latest
LABEL maintainer="garandplg@garandplg.com"
LABEL version="0.0.26"
LABEL version="0.0.27"
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.26",
"version": "0.0.27",
"private": true,
"type": "module",
"scripts": {

View File

@@ -56,7 +56,7 @@ declare global {
const DropdownMenuSubTrigger: typeof import('../components/ui/dropdown-menu/index')['DropdownMenuSubTrigger']
const DropdownMenuTrigger: typeof import('../components/ui/dropdown-menu/index')['DropdownMenuTrigger']
const EffectScope: typeof import('vue')['EffectScope']
const FORM_ITEM_INJECTION_KEY: typeof import('../components/ui/form/injectionKeys')['FORM_ITEM_INJECTION_KEY']
const FORM_ITEM_INJECTION_KEY: typeof import('../components/ui/form/index')['FORM_ITEM_INJECTION_KEY']
const Form: typeof import('../components/ui/form/index')['Form']
const FormControl: typeof import('../components/ui/form/index')['FormControl']
const FormDescription: typeof import('../components/ui/form/index')['FormDescription']
@@ -207,6 +207,7 @@ declare global {
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
@@ -388,6 +389,7 @@ declare global {
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useProcessEndTurnCollectionsData: typeof import('../composables/useProcessEndTurnCollectionsData')['default']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
@@ -396,6 +398,7 @@ declare global {
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
@@ -535,7 +538,7 @@ declare module 'vue' {
readonly DropdownMenuSubTrigger: UnwrapRef<typeof import('../components/ui/dropdown-menu/index')['DropdownMenuSubTrigger']>
readonly DropdownMenuTrigger: UnwrapRef<typeof import('../components/ui/dropdown-menu/index')['DropdownMenuTrigger']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly FORM_ITEM_INJECTION_KEY: UnwrapRef<typeof import('../components/ui/form/injectionKeys')['FORM_ITEM_INJECTION_KEY']>
readonly FORM_ITEM_INJECTION_KEY: UnwrapRef<typeof import('../components/ui/form/index')['FORM_ITEM_INJECTION_KEY']>
readonly Form: UnwrapRef<typeof import('../components/ui/form/index')['Form']>
readonly FormControl: UnwrapRef<typeof import('../components/ui/form/index')['FormControl']>
readonly FormDescription: UnwrapRef<typeof import('../components/ui/form/index')['FormDescription']>
@@ -686,6 +689,7 @@ declare module 'vue' {
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
@@ -867,6 +871,7 @@ declare module 'vue' {
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useProcessEndTurnCollectionsData: UnwrapRef<typeof import('../composables/useProcessEndTurnCollectionsData')['default']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
@@ -875,6 +880,7 @@ declare module 'vue' {
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>

View File

@@ -88,6 +88,7 @@ declare module 'vue' {
PopoverContent: typeof import('./../components/ui/popover/PopoverContent.vue')['default']
PopoverTrigger: typeof import('./../components/ui/popover/PopoverTrigger.vue')['default']
PopulationAccordion: typeof import('./../components/report/population/populationAccordion.vue')['default']
PopulationGrowth: typeof import('./../components/report/population/populationGrowth.vue')['default']
PopulationReport: typeof import('./../components/report/populationReport.vue')['default']
PreviewReportTab: typeof import('./../components/admin-panel/previewReportTab.vue')['default']
Progress: typeof import('./../components/ui/progress/Progress.vue')['default']

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import customChartTooltip from '@/components/charts-and-stats/customChartTooltip.vue';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/ui/button';
import {
Pagination,
PaginationEllipsis,
@@ -85,6 +85,11 @@ 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')}`;
}
"
/>
</div>
</template>

View File

@@ -1,29 +1,70 @@
<script lang="ts" setup>
defineProps<{
sections: {
value: string;
title: string;
count: number;
type: string;
}[];
tabs: {
name: string;
value: string;
tab: {
turn: number;
}[];
}[];
provincesCategories: string[];
provincesColors: string[];
burgsTabCategories: string[];
burgsTab: {
turn: number;
}[];
import type { BurgsResponse, ProvincesResponse, TurnsResponse } from '@/types/pb-types';
const props = defineProps<{
turnRecords: TurnsResponse<unknown>[];
burgs: BurgsResponse<unknown>[];
provinces: ProvincesResponse<unknown>[];
}>();
const createDataTab = (data: any[], field: string) =>
props.turnRecords.slice(0, -1).map(turn => ({
turn: turn.value,
...data
.filter(item => item.turn === turn.id)
.reduce(
(acc: object, item: any) => ({
...acc,
[item.name]: item[field]
}),
{}
)
}));
const burgsTab = createDataTab(props.burgs, 'population');
const burgsTabCategories: string[] = Object.keys(burgsTab[burgsTab.length - 1]).filter(
key => key !== 'turn'
);
const provincesColorsTab = createDataTab(props.provinces, 'color');
const provincesRuralPopulationsTab = createDataTab(props.provinces, 'ruralPopulation');
const provincesUrbanPopulationsTab = createDataTab(props.provinces, 'urbanPopulation');
const lastRecord = provincesColorsTab[provincesColorsTab.length - 1];
const provincesCategories = Object.keys(lastRecord).filter(key => key !== 'turn');
const provincesColors = Object.values(lastRecord).filter(value => typeof value !== 'number');
const tabs = [
{
name: 'Populacja wiejska',
value: 'ruralPopulation',
tab: provincesRuralPopulationsTab
},
{
name: 'Populacja Miejska',
value: 'urbanPopulation',
tab: provincesUrbanPopulationsTab
}
];
const sections = [
{
value: 'provinces',
title: 'Prowincje',
count: provincesCategories.length,
type: 'provinces'
},
{
value: 'burgs',
title: 'Miasta',
count: burgsTabCategories.length,
type: 'burgs'
}
];
</script>
<template>
<Accordion type="single" class="w-full" collapsible>
<Accordion type="single" class="w-full" collapsible :default-value="sections[0].value">
<AccordionItem v-for="section in sections" :key="section.value" :value="section.value">
<AccordionTrigger class="items-center justify-center space-x-1">
<h3 class="text-lg font-bold text-primary"> {{ section.title }} </h3>

View File

@@ -0,0 +1,134 @@
<script lang="ts" setup>
const props = defineProps<{
rural: number;
urban: number;
growthRate: number;
}>();
const growthRatePerMille = computed(() => props.growthRate / 1000);
const ruralData = computed(() => ({
current: props.rural,
change: Math.floor(props.rural * growthRatePerMille.value),
growth: props.rural + Math.floor(props.rural * growthRatePerMille.value)
}));
const urbanData = computed(() => ({
current: props.urban,
change: Math.floor(props.urban * growthRatePerMille.value),
growth: props.urban + Math.floor(props.urban * growthRatePerMille.value)
}));
const totalData = computed(() => ({
current: ruralData.value.current + urbanData.value.current,
change: ruralData.value.change + urbanData.value.change,
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';
return 'text-orange-500';
};
const getChangeSymbol = (change: number) => {
if (change > 0) return '↑';
if (change < 0) return '↓';
return '→';
};
const formatChange = (change: number) => {
const prefix = change > 0 ? '+' : '';
return `${prefix}${formatNumber(change)}`;
};
</script>
<template>
<div class="mx-auto max-w-4xl space-y-8 p-6">
<div class="text-center">
<h1 class="text-3xl font-bold">
Przyrost naturalny:
<span class="text-primary">{{ props.growthRate }}</span>
</h1>
</div>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="stat-card">
<h2 class="mb-4 text-xl font-semibold">
Populacja <span class="text-primary">wiejska</span>
</h2>
<div class="numbers-display">
<div class="text-2xl font-bold">
{{ formatNumber(ruralData.current) }}
</div>
<div :class="getChangeColor(ruralData.change)">
{{ getChangeSymbol(ruralData.change) }}
{{ formatChange(ruralData.change) }}
</div>
<div class="text-xl">
{{ formatNumber(ruralData.growth) }}
</div>
</div>
</div>
<div class="stat-card">
<h2 class="mb-4 text-xl font-semibold">
Populacja <span class="text-primary">miejska</span>
</h2>
<div class="numbers-display">
<div class="text-2xl font-bold">
{{ formatNumber(urbanData.current) }}
</div>
<div :class="getChangeColor(urbanData.change)">
{{ getChangeSymbol(urbanData.change) }}
{{ formatChange(urbanData.change) }}
</div>
<div class="text-xl">
{{ formatNumber(urbanData.growth) }}
</div>
</div>
</div>
</div>
<div class="stat-card">
<h2 class="mb-4 text-xl font-semibold">
<span class="text-primary">Podsumowanie</span>
</h2>
<div class="flex items-center justify-center space-x-4">
<span class="text-2xl font-bold">
{{ formatNumber(totalData.current) }}
</span>
<div class="flex flex-col items-center">
<span :class="getChangeColor(totalData.change)">
{{ getChangeSymbol(totalData.change) }}
{{ formatChange(totalData.change) }}
</span>
</div>
<span class="text-2xl">
{{ formatNumber(totalData.growth) }}
</span>
</div>
</div>
</div>
</template>
<style scoped>
.stat-card {
@apply rounded-lg rounded-md border bg-background p-6 text-center shadow-md;
}
.numbers-display {
@apply flex flex-col items-center space-y-2;
}
</style>

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup>
// TODO: Populacja
import type { StatesResponse, TurnsResponse } from '@/types/pb-types';
const props = defineProps<{
@@ -15,78 +14,24 @@ const { turnRecords, burgs, provinces } = await useFetchPopulationReport(
!['moderator', 'admin'].includes(currentUser.value?.role),
currentUser.value?.id ? `"${currentUser.value.id}"` : null
);
const createDataTab = (data: any[], field: string) =>
turnRecords.value.slice(0, -1).map(turn => ({
turn: turn.value,
...data
.filter(item => item.turn === turn.id)
.reduce(
(acc: object, item: any) => ({
...acc,
[item.name]: item[field]
}),
{}
)
}));
const burgsTab = createDataTab(burgs.value, 'population');
const burgsTabCategories: string[] = Object.keys(burgsTab[burgsTab.length - 1]).filter(
key => key !== 'turn'
);
const provincesColorsTab = createDataTab(provinces.value, 'color');
const provincesRuralPopulationsTab = createDataTab(provinces.value, 'ruralPopulation');
const provincesUrbanPopulationsTab = createDataTab(provinces.value, 'urbanPopulation');
const lastRecord = provincesColorsTab[provincesColorsTab.length - 1];
const provincesCategories = Object.keys(lastRecord).filter(key => key !== 'turn');
const provincesColors: string[] = Object.values(lastRecord).filter(
value => typeof value !== 'number'
);
const tabs = [
{
name: 'Populacja wiejska',
value: 'ruralPopulation',
tab: provincesRuralPopulationsTab
},
{
name: 'Populacja Miejska',
value: 'urbanPopulation',
tab: provincesUrbanPopulationsTab
}
];
const sections = [
{
value: 'provinces',
title: 'Prowincje',
count: provincesCategories.length,
type: 'provinces'
},
{
value: 'burgs',
title: 'Miasta',
count: burgsTabCategories.length,
type: 'burgs'
}
];
</script>
<template>
<div>
<div class="h-[500px]">placeholder content</div>
<Separator class="my-2" />
<div class="grid grid-cols-12">
<div class="col-span-4 flex w-full flex-col items-center justify-center">
<PopulationGrowth
class="mb-5"
:rural="state.ruralPopulation"
:urban="state.urbanPopulation"
:growthRate="0.5"
/>
</div>
<PopulationAccordion
:sections="sections"
:tabs="tabs"
:provincesCategories="provincesCategories"
:provincesColors="provincesColors"
:burgsTabCategories="burgsTabCategories"
:burgsTab="burgsTab"
:turnRecords="turnRecords"
:burgs="burgs"
:provinces="provinces"
class="col-span-8"
/>
</div>
</template>

View File

@@ -40,7 +40,7 @@ export default async function (
);
const cultureNames = await useCachedData({
key: `${turn.value + 1} -- cultureNames`,
key: `${state.name}-${turn.value + 1}-${isUserNotAdmin ? user : 'Administration'} -- cultureNames`,
collection: 'cultures',
ttl: 1800,
requestArgs: {
@@ -50,7 +50,7 @@ export default async function (
});
const religionNames = await useCachedData({
key: `${turn.value + 1} -- religionNames`,
key: `${state.name}-${turn.value + 1}-${isUserNotAdmin ? user : 'Administration'} -- religionNames`,
collection: 'religions',
ttl: 1800,
requestArgs: {

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.26 Indev</span></p>
<p class="mb-4 text-sm">Wersja: <span class="text-primary">0.0.27 Indev</span></p>
<a
href="https://discord.gg/vhBmFVpR3w"

View File

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