Refactor Dashboard, Graphics, Players, and Settings views for improved layout and styling consistency

This commit is contained in:
2026-05-13 03:20:28 +02:00
parent d8d3c7f03c
commit 21d885f6e6
4 changed files with 74 additions and 51 deletions
@@ -11,9 +11,10 @@ useHead({ title: 'Dashboard' });
<template> <template>
<QPage class="q-pa-lg"> <QPage class="q-pa-lg">
<div class="dashboard-panels q-mt-lg"> <div class="dashboard-panels">
<div class="dashboard-row dashboard-row--scoreboard"> <div class="dashboard-row dashboard-row--scoreboard">
<QCard <QCard
flat
bordered bordered
class="dashboard-panel-card" class="dashboard-panel-card"
> >
@@ -25,6 +26,7 @@ useHead({ title: 'Dashboard' });
<div class="dashboard-row dashboard-row--bottom"> <div class="dashboard-row dashboard-row--bottom">
<QCard <QCard
flat
bordered bordered
class="dashboard-panel-card" class="dashboard-panel-card"
> >
@@ -33,6 +35,7 @@ useHead({ title: 'Dashboard' });
</QCardSection> </QCardSection>
</QCard> </QCard>
<QCard <QCard
flat
bordered bordered
class="dashboard-panel-card" class="dashboard-panel-card"
> >
@@ -53,20 +56,12 @@ useHead({ title: 'Dashboard' });
gap: 24px; gap: 24px;
} }
.dashboard-row {
width: 100%;
}
.dashboard-row--bottom { .dashboard-row--bottom {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 24px; gap: 24px;
} }
.dashboard-panel-card {
width: 100%;
}
.dashboard-panel-content { .dashboard-panel-content {
padding-bottom: 16px; padding-bottom: 16px;
} }
+15 -12
View File
@@ -1,14 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { useHead } from '@unhead/vue';
import bundlePackage from '../../../../package.json';
import { graphicsSettingsReplicant } from '../../../browser_shared/replicants'; import { graphicsSettingsReplicant } from '../../../browser_shared/replicants';
import { t } from '../i18n'; import { t } from '../i18n';
defineOptions({ name: 'GraphicsView' }); defineOptions({ name: 'GraphicsView' });
import bundlePackage from '../../../../package.json'; useHead(() => ({ title: t('graphicsTitle') }));type GraphicConfig = {
type GraphicConfig = {
name?: string; name?: string;
title?: string; title?: string;
file: string; file: string;
@@ -165,16 +164,18 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
<template> <template>
<QPage class="q-pa-lg"> <QPage class="q-pa-lg">
<div class="text-h4 q-mb-md"> <div class="q-mb-lg">
{{ t('graphicsTitle') }} <div class="text-h5 text-weight-medium">
</div> {{ t('graphicsTitle') }}
<div class="text-body1 q-mb-lg"> </div>
{{ t('graphicsDescription') }} <div class="text-body2 text-grey-7 q-mt-xs">
{{ t('graphicsDescription') }}
</div>
</div> </div>
<div <div
v-if="cards.length === 0" v-if="cards.length === 0"
class="text-body2 text-grey-5" class="text-body2 text-grey-6"
> >
{{ t('graphicsNoConfigured') }} {{ t('graphicsNoConfigured') }}
</div> </div>
@@ -194,7 +195,7 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
<div class="text-h6"> <div class="text-h6">
{{ card.label }} {{ card.label }}
</div> </div>
<div class="text-caption text-grey-5"> <div class="text-caption text-grey-4">
{{ card.graphic.file }} {{ card.graphic.file }}
</div> </div>
</div> </div>
@@ -226,14 +227,16 @@ const onDragStart = (event: DragEvent, graphic: GraphicConfig) => {
<QBtn <QBtn
color="primary" color="primary"
icon="content_copy" icon="content_copy"
no-caps
:label="t('graphicsCopyUrl')" :label="t('graphicsCopyUrl')"
@click="copyUrl(card.graphic)" @click="copyUrl(card.graphic)"
/> />
<QBtn <QBtn
color="secondary" color="secondary"
icon="open_with" icon="open_with"
:label="t('graphicsDragObs')" no-caps
draggable="true" draggable="true"
:label="t('graphicsDragObs')"
@dragstart="onDragStart($event, card.graphic)" @dragstart="onDragStart($event, card.graphic)"
/> />
</div> </div>
+28 -9
View File
@@ -1,15 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useHead } from '@unhead/vue';
defineOptions({ name: 'PlayersView' });
import type { QTableColumn } from 'quasar';
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { useHead } from '@unhead/vue';
import type { QTableColumn } from 'quasar';
import { getCountryLabel, getCountryOptions } from '../../../shared/countries'; import { getCountryLabel, getCountryOptions } from '../../../shared/countries';
import type { Schemas } from '../../../types'; import type { Schemas } from '../../../types';
import { locale, t } from '../i18n'; import { locale, t } from '../i18n';
import { usePlayersStore } from '../stores/players'; import { usePlayersStore } from '../stores/players';
defineOptions({ name: 'PlayersView' });
useHead(() => ({ title: t('menuPlayers') })); useHead(() => ({ title: t('menuPlayers') }));
type PlayersMap = Schemas.Players; type PlayersMap = Schemas.Players;
@@ -810,13 +809,14 @@ onBeforeUnmount(() => {
<template> <template>
<QPage class="q-pa-lg players-page"> <QPage class="q-pa-lg players-page">
<div class="row items-center q-mb-md"> <div class="row items-center q-mb-md">
<div class="text-h4"> <div class="text-h5 text-weight-medium">
{{ t('menuPlayers') }} {{ t('menuPlayers') }}
</div> </div>
<QSpace /> <QSpace />
<QBtn <QBtn
color="primary" color="primary"
icon="add" icon="add"
no-caps
:label="t('playersNewPlayer')" :label="t('playersNewPlayer')"
class="q-ml-sm" class="q-ml-sm"
@click="openCreateDialog" @click="openCreateDialog"
@@ -841,6 +841,7 @@ onBeforeUnmount(() => {
color="secondary" color="secondary"
outline outline
icon="file_upload" icon="file_upload"
no-caps
:label="t('playersImport')" :label="t('playersImport')"
@click="triggerImport" @click="triggerImport"
/> />
@@ -848,6 +849,7 @@ onBeforeUnmount(() => {
color="secondary" color="secondary"
outline outline
icon="file_download" icon="file_download"
no-caps
:label="t('playersExport')" :label="t('playersExport')"
@click="exportPlayers" @click="exportPlayers"
/> />
@@ -908,7 +910,7 @@ onBeforeUnmount(() => {
</svg> </svg>
<span>start.gg</span> <span>start.gg</span>
</div> </div>
<div class="text-caption q-mb-md"> <div class="text-caption text-grey-6 q-mb-md">
{{ t('playersStartggHelp') }} {{ t('playersStartggHelp') }}
</div> </div>
<div class="row q-col-gutter-sm items-center"> <div class="row q-col-gutter-sm items-center">
@@ -917,6 +919,7 @@ onBeforeUnmount(() => {
v-if="!hasStartGGTokenConfigured" v-if="!hasStartGGTokenConfigured"
color="primary" color="primary"
icon="login" icon="login"
no-caps
:label="t('playersConnectStartgg')" :label="t('playersConnectStartgg')"
:loading="oauthLoading" :loading="oauthLoading"
@click="connectWithStartGGOAuth" @click="connectWithStartGGOAuth"
@@ -926,6 +929,7 @@ onBeforeUnmount(() => {
outline outline
color="positive" color="positive"
icon="check_circle" icon="check_circle"
no-caps
:label="t('playersConnected')" :label="t('playersConnected')"
class="startgg-connected-btn" class="startgg-connected-btn"
@click="openManualTokenDialog" @click="openManualTokenDialog"
@@ -936,6 +940,7 @@ onBeforeUnmount(() => {
outline outline
color="white" color="white"
icon="vpn_key" icon="vpn_key"
no-caps
:label="t('playersUsePersonalApi')" :label="t('playersUsePersonalApi')"
@click="openManualTokenDialog" @click="openManualTokenDialog"
/> />
@@ -1020,7 +1025,7 @@ onBeforeUnmount(() => {
> >
<span>Challonge</span> <span>Challonge</span>
</div> </div>
<div class="text-caption q-mb-md"> <div class="text-caption text-grey-6 q-mb-md">
{{ t('playersChallongeHelp') }} {{ t('playersChallongeHelp') }}
</div> </div>
<div class="row q-col-gutter-sm items-center"> <div class="row q-col-gutter-sm items-center">
@@ -1029,6 +1034,7 @@ onBeforeUnmount(() => {
v-if="!hasChallongeTokenConfigured" v-if="!hasChallongeTokenConfigured"
color="primary" color="primary"
icon="login" icon="login"
no-caps
:label="t('playersConnectChallonge')" :label="t('playersConnectChallonge')"
:loading="challongeOauthLoading" :loading="challongeOauthLoading"
@click="connectWithChallongeOAuth" @click="connectWithChallongeOAuth"
@@ -1038,6 +1044,7 @@ onBeforeUnmount(() => {
outline outline
:color="hasValidatedChallongeToken ? 'positive' : 'warning'" :color="hasValidatedChallongeToken ? 'positive' : 'warning'"
icon="check_circle" icon="check_circle"
no-caps
:label="challongeConnectionLabel" :label="challongeConnectionLabel"
@click="openChallongeManualTokenDialog" @click="openChallongeManualTokenDialog"
/> />
@@ -1047,6 +1054,7 @@ onBeforeUnmount(() => {
outline outline
color="white" color="white"
icon="vpn_key" icon="vpn_key"
no-caps
:label="t('playersUsePersonalApi')" :label="t('playersUsePersonalApi')"
@click="openChallongeManualTokenDialog" @click="openChallongeManualTokenDialog"
/> />
@@ -1121,7 +1129,6 @@ onBeforeUnmount(() => {
</div> </div>
</div> </div>
<QDialog v-model="isManualTokenDialogOpen"> <QDialog v-model="isManualTokenDialogOpen">
<QCard class="players-dialog"> <QCard class="players-dialog">
<QCardSection> <QCardSection>
@@ -1153,17 +1160,20 @@ onBeforeUnmount(() => {
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
flat flat
no-caps
label="Cancel" label="Cancel"
color="secondary" color="secondary"
@click="isManualTokenDialogOpen = false" @click="isManualTokenDialogOpen = false"
/> />
<QBtn <QBtn
flat flat
no-caps
color="negative" color="negative"
label="Delete token" label="Delete token"
@click="manualTokenDraft = ''; saveManualToken()" @click="manualTokenDraft = ''; saveManualToken()"
/> />
<QBtn <QBtn
no-caps
color="primary" color="primary"
label="Save token" label="Save token"
@click="saveManualToken" @click="saveManualToken"
@@ -1203,11 +1213,13 @@ onBeforeUnmount(() => {
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
flat flat
no-caps
label="Cancel" label="Cancel"
color="secondary" color="secondary"
@click="isImportDialogOpen = false" @click="isImportDialogOpen = false"
/> />
<QBtn <QBtn
no-caps
color="primary" color="primary"
label="Import selected" label="Import selected"
:disable="!selectedStartGGPlayerIds.length" :disable="!selectedStartGGPlayerIds.length"
@@ -1248,11 +1260,13 @@ onBeforeUnmount(() => {
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
flat flat
no-caps
label="Cancel" label="Cancel"
color="secondary" color="secondary"
@click="challongeImportDialogOpen = false" @click="challongeImportDialogOpen = false"
/> />
<QBtn <QBtn
no-caps
color="primary" color="primary"
label="Import selected" label="Import selected"
:disable="!selectedChallongePlayerIds.length" :disable="!selectedChallongePlayerIds.length"
@@ -1286,17 +1300,20 @@ onBeforeUnmount(() => {
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
flat flat
no-caps
label="Cancel" label="Cancel"
color="secondary" color="secondary"
@click="isChallongeManualTokenDialogOpen = false" @click="isChallongeManualTokenDialogOpen = false"
/> />
<QBtn <QBtn
flat flat
no-caps
color="negative" color="negative"
label="Delete token" label="Delete token"
@click="challongeManualTokenDraft = ''; saveChallongeManualToken()" @click="challongeManualTokenDraft = ''; saveChallongeManualToken()"
/> />
<QBtn <QBtn
no-caps
color="primary" color="primary"
label="Save token" label="Save token"
@click="saveChallongeManualToken" @click="saveChallongeManualToken"
@@ -1376,11 +1393,13 @@ onBeforeUnmount(() => {
<QCardActions align="right"> <QCardActions align="right">
<QBtn <QBtn
flat flat
no-caps
label="Cancel" label="Cancel"
color="secondary" color="secondary"
@click="isDialogOpen = false" @click="isDialogOpen = false"
/> />
<QBtn <QBtn
no-caps
color="primary" color="primary"
label="Save" label="Save"
@click="savePlayer" @click="savePlayer"
+27 -21
View File
@@ -11,6 +11,8 @@ import {
defineOptions({ name: 'SettingsView' }); defineOptions({ name: 'SettingsView' });
useHead(() => ({ title: t('settingsTitle') }));
const languageOptions = computed(() => [ const languageOptions = computed(() => [
{ label: t('languageSpanish'), value: 'es' as const }, { label: t('languageSpanish'), value: 'es' as const },
{ label: t('languageEnglish'), value: 'en' as const }, { label: t('languageEnglish'), value: 'en' as const },
@@ -77,26 +79,27 @@ onBeforeUnmount(() => {
} }
stopRecording(); stopRecording();
}); });
useHead(() => ({ title: t('settingsTitle') }));
</script> </script>
<template> <template>
<QPage class="q-pa-lg"> <QPage class="q-pa-lg">
<div class="text-h4 q-mb-md"> <div class="q-mb-lg">
{{ t('settingsTitle') }} <div class="text-h5 text-weight-medium">
</div> {{ t('settingsTitle') }}
<div class="text-body1 q-mb-lg"> </div>
{{ t('settingsDescription') }} <div class="text-body2 text-grey-7 q-mt-xs">
{{ t('settingsDescription') }}
</div>
</div> </div>
<QCard <QCard
flat flat
bordered bordered
class="q-pa-md settings-card" class="settings-card"
> >
<QCardSection class="q-pa-none q-mb-lg"> <!-- Language -->
<div class="text-subtitle1 q-mb-sm"> <QCardSection class="q-pa-lg">
<div class="text-overline text-grey-6 q-mb-md">
{{ t('settingsLanguageLabel') }} {{ t('settingsLanguageLabel') }}
</div> </div>
@@ -104,20 +107,22 @@ useHead(() => ({ title: t('settingsTitle') }));
v-model="selectedLanguage" v-model="selectedLanguage"
emit-value emit-value
map-options map-options
:label="t('settingsLanguageLabel')"
:options="languageOptions" :options="languageOptions"
outlined
dense
/> />
<div class="text-caption text-grey-5 q-mt-sm"> <div class="text-caption text-grey-6 q-mt-sm">
{{ t('settingsLanguageHint') }} {{ t('settingsLanguageHint') }}
</div> </div>
</QCardSection> </QCardSection>
<QSeparator class="q-mb-lg" /> <QSeparator />
<QCardSection class="q-pa-none"> <!-- Shortcuts -->
<div class="row items-center justify-between q-mb-sm"> <QCardSection class="q-pa-lg">
<div class="text-subtitle1"> <div class="row items-center justify-between q-mb-xs">
<div class="text-overline text-grey-6">
{{ t('settingsShortcutTitle') }} {{ t('settingsShortcutTitle') }}
</div> </div>
<QBtn <QBtn
@@ -133,7 +138,7 @@ useHead(() => ({ title: t('settingsTitle') }));
</QBtn> </QBtn>
</div> </div>
<div class="text-caption text-grey-5 q-mb-md"> <div class="text-caption text-grey-6 q-mb-lg">
{{ t('settingsShortcutDescription') }} {{ t('settingsShortcutDescription') }}
</div> </div>
@@ -142,7 +147,11 @@ useHead(() => ({ title: t('settingsTitle') }));
v-for="field in shortcutFields" v-for="field in shortcutFields"
:key="field.action" :key="field.action"
:model-value="shortcutSettingsStore.shortcuts[field.action]" :model-value="shortcutSettingsStore.shortcuts[field.action]"
:hint="recordingAction === field.action ? t('settingsShortcutRecordingHint') : field.hint"
readonly readonly
outlined
dense
bottom-slots
:label="field.label" :label="field.label"
> >
<template #append> <template #append>
@@ -155,9 +164,6 @@ useHead(() => ({ title: t('settingsTitle') }));
@click="startRecording(field.action)" @click="startRecording(field.action)"
/> />
</template> </template>
<template #hint>
{{ recordingAction === field.action ? t('settingsShortcutRecordingHint') : field.hint }}
</template>
</QInput> </QInput>
</div> </div>
</QCardSection> </QCardSection>
@@ -167,6 +173,6 @@ useHead(() => ({ title: t('settingsTitle') }));
<style scoped> <style scoped>
.settings-card { .settings-card {
max-width: 720px; max-width: 600px;
} }
</style> </style>