Files
imen_pishghaman/apps/admin/src/pages/SectionsPage.vue
T

159 lines
5.0 KiB
Vue

<script setup lang="ts">
import { SECTION_KEYS, SECTION_LABELS, type SectionKey } from "@imen/shared";
import { message } from "ant-design-vue";
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { api, errorMessage } from "@/api/http";
import type { AdminSectionSummary } from "@/api/types";
import PageHeader from "@/components/PageHeader.vue";
import { useAuthStore } from "@/stores/auth";
import { jalali } from "@/utils/format";
const router = useRouter();
const auth = useAuthStore();
// site-wide settings are editable by admins only
const keys = SECTION_KEYS.filter((k) => k !== "settings" || auth.isAdmin);
const loading = ref(true);
const sectionEnabled = reactive<Record<string, boolean>>({
hero: true,
tape: true,
stats: true,
productsIntro: true,
about: true,
services: true,
standards: true,
industries: true,
testimonials: true,
contact: true,
footer: true,
});
const sectionUpdatedAt = reactive<Record<string, string | null>>({});
const toggling = reactive<Record<string, boolean>>({});
function isEnabled(key: SectionKey): boolean {
return sectionEnabled[key] !== false;
}
function getUpdatedAt(key: SectionKey): string | null {
return sectionUpdatedAt[key] ?? null;
}
async function fetchSections() {
loading.value = true;
try {
const list = await api<AdminSectionSummary[]>("/admin/sections");
for (const item of list) {
sectionEnabled[item.key] = item.enabled !== false;
sectionUpdatedAt[item.key] = item.updatedAt;
}
} catch (e) {
void message.error(errorMessage(e));
} finally {
loading.value = false;
}
}
async function toggle(key: SectionKey, nextVal: boolean) {
if (key === "settings") return;
const prevVal = sectionEnabled[key] !== false;
sectionEnabled[key] = nextVal;
toggling[key] = true;
try {
const res = await api<{ key: string; enabled: boolean; updatedAt: string }>(
`/admin/sections/${key}/visibility`,
{
method: "PATCH",
body: { enabled: nextVal },
},
);
sectionEnabled[key] = res.enabled !== false;
sectionUpdatedAt[key] = res.updatedAt;
void message.success(
`وضعیت «${SECTION_LABELS[key].title}» به ${nextVal ? "نمایش" : "عدم نمایش"} تغییر یافت`,
);
} catch (e) {
sectionEnabled[key] = prevVal;
void message.error(errorMessage(e));
} finally {
toggling[key] = false;
}
}
function goToEdit(key: SectionKey) {
void router.push({ name: "section-edit", params: { key } });
}
onMounted(() => {
void fetchSections();
});
</script>
<template>
<PageHeader
title="بخش‌های سایت"
subtitle="متن‌ها، تصاویر و فهرست‌های صفحه اصلی؛ تنظیم وضعیت نمایش و ویرایش محتوا"
/>
<a-spin :spinning="loading">
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
<div
v-for="key in keys"
:key="key"
class="block group"
>
<a-card
hoverable
class="h-full flex flex-col justify-between transition-all"
:class="{ 'opacity-75 bg-gray-50/60': key !== 'settings' && !isEnabled(key) }"
>
<div>
<div class="flex items-center justify-between gap-2">
<div
class="flex items-center gap-2 cursor-pointer flex-1"
@click="goToEdit(key)"
>
<span class="font-bold text-gray-800 group-hover:text-orange-600 transition-colors">
{{ SECTION_LABELS[key].title }}
</span>
<a-tag v-if="key === 'settings'" color="blue">تنظیمات پایه</a-tag>
<a-tag v-else-if="!isEnabled(key)" color="error">مخفی</a-tag>
</div>
<!-- Switch toggle: standalone button without any outer anchor tag -->
<div
v-if="key !== 'settings'"
class="flex items-center flex-shrink-0"
>
<a-switch
:checked="isEnabled(key)"
:loading="toggling[key]"
checked-children="نمایش"
un-checked-children="مخفی"
@change="(val: any) => toggle(key, Boolean(val))"
/>
</div>
</div>
<p
class="mt-2 text-sm leading-6 text-gray-500 cursor-pointer"
@click="goToEdit(key)"
>
{{ SECTION_LABELS[key].desc }}
</p>
</div>
<div
class="mt-4 flex items-center justify-between border-t border-gray-100 pt-3 text-xs text-gray-400 cursor-pointer"
@click="goToEdit(key)"
>
<span class="text-primary font-medium group-hover:underline">ویرایش بخش ←</span>
<span v-if="getUpdatedAt(key)">آخرین تغییر: {{ jalali(getUpdatedAt(key)) }}</span>
</div>
</a-card>
</div>
</div>
</a-spin>
</template>