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