feat: add settings data upload and single-name user forms

Allow SuperAdmins to upload import JSON from settings, and update user UI for merged name, gender, and extended profile fields.
This commit is contained in:
2026-08-15 01:31:47 +03:30
parent 833584b1b4
commit 6b5549019c
11 changed files with 268 additions and 27 deletions
+112 -2
View File
@@ -3,10 +3,10 @@
<div class="settings-view w-full max-w-3xl mx-auto">
<PageHeader
title="تنظیمات سیستم"
subtitle="عملیات راه‌اندازی و پیکربندی اولیه پایگاه داده"
subtitle="عملیات راه‌اندازی و وارد کردن داده‌های دوره‌ها و کاربران"
/>
<div class="surface-card p-4 sm:p-5 border-round-xl border-1 border-color shadow-sm">
<div class="surface-card p-4 sm:p-5 border-round-xl border-1 border-color shadow-sm mb-4">
<div class="flex align-items-start gap-3 mb-4">
<div class="w-3rem h-3rem border-round-xl flex align-items-center justify-content-center flex-shrink-0 bg-primary-light text-primary">
<i class="pi pi-database text-xl"></i>
@@ -45,6 +45,67 @@
</span>
</div>
</div>
<div class="surface-card p-4 sm:p-5 border-round-xl border-1 border-color shadow-sm">
<div class="flex align-items-start gap-3 mb-4">
<div class="w-3rem h-3rem border-round-xl flex align-items-center justify-content-center flex-shrink-0 bg-primary-light text-primary">
<i class="pi pi-upload text-xl"></i>
</div>
<div class="flex-grow-1">
<h2 class="text-lg font-bold text-color m-0 mb-1">وارد کردن دادهها</h2>
<p class="text-sm text-muted m-0 line-height-3">
فایل JSON ساختاریافته دورهها، کلاسها و کاربران را بارگذاری کنید. دادههای قبلی حذف نمیشوند؛ موارد تکراری بهروزرسانی یا به کلاس اضافه میشوند.
</p>
</div>
</div>
<div class="flex flex-column gap-3">
<input
ref="fileInputRef"
type="file"
accept="application/json,.json"
class="hidden"
@change="onFileSelected"
/>
<div class="flex flex-column sm:flex-row align-items-stretch sm:align-items-center gap-3">
<Button
:label="selectedFileName || 'انتخاب فایل JSON'"
:icon="selectedFileName ? 'pi pi-file' : 'pi pi-folder-open'"
severity="secondary"
outlined
class="font-semibold"
@click="fileInputRef?.click()"
/>
<Button
label="بارگذاری و افزودن به پایگاه داده"
icon="pi pi-cloud-upload"
class="font-bold"
:loading="isImporting"
:disabled="!selectedFile"
@click="confirmImport"
/>
</div>
<p class="text-xs text-muted m-0 line-height-3">
نمونه فایل آماده: <code dir="ltr">raw-data/import-data.json</code>
</p>
<div
v-if="importResult"
class="p-3 border-round surface-ground border-1 border-color text-sm"
>
<div class="font-semibold mb-2">نتیجه آخرین بارگذاری</div>
<ul class="m-0 pr-3 line-height-3">
<li>دوره جدید: {{ importResult.stats?.coursesCreated ?? 0 }} بازاستفاده: {{ importResult.stats?.coursesReused ?? 0 }}</li>
<li>کلاس جدید: {{ importResult.stats?.classesCreated ?? 0 }} بازاستفاده: {{ importResult.stats?.classesReused ?? 0 }}</li>
<li>کاربر جدید: {{ importResult.stats?.studentsCreated ?? 0 }} بهروزرسانی: {{ importResult.stats?.studentsUpdated ?? 0 }}</li>
<li>ثبتنام در کلاس: {{ importResult.stats?.enrollmentsAdded ?? 0 }} رد شده: {{ importResult.stats?.studentsSkipped ?? 0 }}</li>
<li v-if="importResult.warnings?.length">هشدارها: {{ importResult.warnings.length }}</li>
</ul>
</div>
</div>
</div>
</div>
</template>
@@ -64,6 +125,12 @@ const isSeeding = ref(false);
const isLocked = ref(false);
const lockedAt = ref(null);
const fileInputRef = ref(null);
const selectedFile = ref(null);
const selectedFileName = ref('');
const isImporting = ref(false);
const importResult = ref(null);
const lockedAtLabel = computed(() => {
if (!lockedAt.value) return '';
const date = new Date(lockedAt.value);
@@ -118,5 +185,48 @@ const confirmSeed = () => {
});
};
const onFileSelected = (event) => {
const file = event.target.files?.[0] || null;
selectedFile.value = file;
selectedFileName.value = file?.name || '';
importResult.value = null;
};
const runImport = async () => {
if (!selectedFile.value) return;
isImporting.value = true;
try {
const response = await seedApi.importData(selectedFile.value);
const data = response?.data || response;
importResult.value = data;
showSuccess('داده‌ها با موفقیت به پایگاه داده اضافه شدند.');
} catch (err) {
showError(err);
} finally {
isImporting.value = false;
}
};
const confirmImport = () => {
if (!selectedFile.value) return;
confirm.require({
header: 'وارد کردن داده‌ها',
message: 'دوره‌ها، کلاس‌ها و کاربران از فایل JSON به پایگاه داده افزوده می‌شوند. داده‌های موجود حذف نمی‌شوند. ادامه می‌دهید؟',
icon: 'pi pi-upload',
acceptLabel: 'بارگذاری',
rejectLabel: 'انصراف',
acceptClass: 'p-button-primary',
accept: () => {
runImport();
}
});
};
onMounted(loadStatus);
</script>
<style scoped>
.hidden {
display: none;
}
</style>