Files

159 lines
4.5 KiB
Vue

<script setup lang="ts">
const { t } = useI18n();
definePageMeta({ middleware: "auth" });
const { $api } = useNuxtApp();
const loading = ref(true);
const errorMessage = ref("");
const stats = reactive({
users: 0,
shops: 0,
orders: 0,
currencies: 0,
});
async function loadDashboard(): Promise<void> {
loading.value = true;
errorMessage.value = "";
try {
const [users, shops, orders, currencies] = await Promise.all([
$api.admin.listUsers(1),
$api.admin.listShops(),
$api.admin.listOrders(1),
$api.admin.listCurrencies(),
]);
stats.users = users.total;
stats.shops = shops.length;
stats.orders = orders.total;
stats.currencies = currencies.filter((currency) => currency.enabled).length;
} catch (error: unknown) {
errorMessage.value = error instanceof Error ? error.message : t("common.error");
} finally {
loading.value = false;
}
}
onMounted(() => {
void loadDashboard();
});
const cards = computed(() => [
{ label: "admin.totalUsers", value: stats.users, link: "/users", tone: "blue" },
{ label: "admin.totalShops", value: stats.shops, link: "/shops", tone: "green" },
{ label: "admin.totalOrders", value: stats.orders, link: "/orders", tone: "orange" },
{ label: "admin.enabledCurrencies", value: stats.currencies, link: "/currencies", tone: "purple" },
]);
</script>
<template>
<div class="page">
<div class="page-head">
<div>
<p class="eyebrow">{{ $t("common.appName") }} · Admin</p>
<h1 class="page-title">{{ $t("admin.dashboardTitle") }}</h1>
</div>
<button class="btn sm" type="button" :disabled="loading" @click="loadDashboard">
{{ $t("admin.refresh") }}
</button>
</div>
<p v-if="loading" class="muted">{{ $t("common.loading") }}</p>
<p v-else-if="errorMessage" class="error-text" role="alert">{{ errorMessage }}</p>
<template v-else>
<div class="stat-grid">
<NuxtLink v-for="card in cards" :key="card.link" :to="card.link" class="stat-card">
<span class="stat-label">{{ $t(card.label) }}</span>
<strong class="stat-value" :class="`tone-${card.tone}`">{{ card.value }}</strong>
<span class="stat-link">{{ $t("admin.open") }} </span>
</NuxtLink>
</div>
<div class="grid shortcuts">
<NuxtLink class="card shortcut" to="/users">
<strong>{{ $t("admin.userManagement") }}</strong>
<span class="muted">{{ $t("nav.users") }}</span>
</NuxtLink>
<NuxtLink class="card shortcut" to="/shops">
<strong>{{ $t("admin.shopManagement") }}</strong>
<span class="muted">{{ $t("nav.shops") }}</span>
</NuxtLink>
<NuxtLink class="card shortcut" to="/orders">
<strong>{{ $t("admin.orderManagement") }}</strong>
<span class="muted">{{ $t("nav.orders") }}</span>
</NuxtLink>
<NuxtLink class="card shortcut" to="/currencies">
<strong>{{ $t("admin.currencyManagement") }}</strong>
<span class="muted">{{ $t("nav.currencies") }}</span>
</NuxtLink>
</div>
</template>
</div>
</template>
<style scoped>
.eyebrow {
color: var(--primary);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
margin: 0 0 6px;
text-transform: uppercase;
}
.stat-grid {
display: grid;
gap: 16px;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.stat-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 8px;
padding: 20px;
text-decoration: none;
transition: transform 150ms ease, border-color 150ms ease;
}
.stat-card:hover {
border-color: var(--primary);
text-decoration: none;
transform: translateY(-2px);
}
.stat-label {
color: var(--muted);
font-size: 13px;
}
.stat-value {
font-size: 32px;
line-height: 1;
}
.tone-blue { color: var(--primary); }
.tone-green { color: var(--success); }
.tone-orange { color: var(--warning); }
.tone-purple { color: #7257b8; }
.stat-link {
color: var(--primary);
font-size: 12px;
}
.shortcuts {
grid-template-columns: repeat(4, minmax(0, 1fr));
margin-top: 24px;
}
.shortcut {
display: flex;
flex-direction: column;
gap: 4px;
text-decoration: none;
}
.shortcut:hover { text-decoration: none; }
@media (max-width: 900px) {
.stat-grid, .shortcuts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
.stat-grid, .shortcuts { grid-template-columns: 1fr; }
}
</style>