159 lines
5.0 KiB
Vue
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>
|