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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user