feat: redesign StaffPage with KPI bar, improved table layout, and enhanced form fields

This commit is contained in:
hamed
2026-06-14 23:36:37 +03:30
parent 5be63a23b6
commit 55f646e2d4
6 changed files with 1118 additions and 258 deletions
+86 -22
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { PencilIcon, EyeIcon, EyeSlashIcon, PlusIcon } from '@heroicons/react/24/outline';
import { PencilIcon, EyeIcon, EyeSlashIcon, PlusIcon, UserGroupIcon } from '@heroicons/react/24/outline';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -38,6 +38,9 @@ export default function StaffPage() {
});
const staff = data?.data ?? EMPTY;
const total = staff.length;
const active = staff.filter((s) => s.active).length;
const inactive = total - active;
const createForm = useForm<StaffFormData>({ resolver: zodResolver(schema) });
const editForm = useForm<StaffFormData>({ resolver: zodResolver(schema) });
@@ -86,10 +89,36 @@ export default function StaffPage() {
};
const columns: Column<ClinicStaff>[] = [
{ key: 'full_name', header: 'نام و نام خانوادگی' },
{ key: 'job_title', header: 'سمت', render: (s) => s.job_title ?? '—' },
{ key: 'phone', header: 'تلفن', render: (s) => s.phone ?? '—' },
{ key: 'national_code', header: 'کد ملی', render: (s) => s.national_code ?? '—' },
{
key: 'full_name',
header: 'پرسنل',
render: (s) => (
<div>
<div style={{ fontWeight: 600, fontSize: 14 }}>{s.full_name}</div>
{s.job_title && (
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 2 }}>{s.job_title}</div>
)}
</div>
),
},
{
key: 'phone',
header: 'تلفن',
render: (s) => (
<span style={{ fontSize: 13, direction: 'ltr', display: 'inline-block' }}>
{s.phone ?? '—'}
</span>
),
},
{
key: 'national_code',
header: 'کد ملی',
render: (s) => (
<span style={{ fontSize: 13, direction: 'ltr', display: 'inline-block' }}>
{s.national_code ?? '—'}
</span>
),
},
{
key: 'active',
header: 'وضعیت',
@@ -98,7 +127,9 @@ export default function StaffPage() {
{
key: 'created_at',
header: 'تاریخ ثبت',
render: (s) => formatDate(s.created_at),
render: (s) => (
<span style={{ fontSize: 13, color: 'var(--text-3)' }}>{formatDate(s.created_at)}</span>
),
},
{
key: 'uuid',
@@ -135,8 +166,37 @@ export default function StaffPage() {
}
/>
{/* KPI bar */}
{!isLoading && (
<div className="stat-grid" style={{ marginBottom: 16, gridTemplateColumns: 'repeat(3, 1fr)' }}>
<div className="card card-pad" style={{ textAlign: 'center' }}>
<div style={{ fontSize: 28, fontWeight: 800, color: 'var(--text-1)' }}>{total}</div>
<div style={{ fontSize: 13, color: 'var(--text-3)', marginTop: 2 }}>کل پرسنل</div>
</div>
<div className="card card-pad" style={{ textAlign: 'center' }}>
<div style={{ fontSize: 28, fontWeight: 800, color: 'oklch(0.55 0.16 162)' }}>{active}</div>
<div style={{ fontSize: 13, color: 'var(--text-3)', marginTop: 2 }}>فعال</div>
</div>
<div className="card card-pad" style={{ textAlign: 'center' }}>
<div style={{ fontSize: 28, fontWeight: 800, color: 'oklch(0.55 0.18 25)' }}>{inactive}</div>
<div style={{ fontSize: 13, color: 'var(--text-3)', marginTop: 2 }}>غیرفعال</div>
</div>
</div>
)}
<div className="card">
<DataTable columns={columns} data={staff} loading={isLoading} />
{staff.length === 0 && !isLoading ? (
<div style={{ textAlign: 'center', padding: '60px 24px', color: 'var(--text-3)' }}>
<UserGroupIcon style={{ width: 48, margin: '0 auto 16px', display: 'block', opacity: 0.4 }} />
<div style={{ fontWeight: 600, fontSize: 15, marginBottom: 8, color: 'var(--text-2)' }}>هنوز پرسنلی ثبت نشده</div>
<div style={{ fontSize: 13, marginBottom: 20 }}>اولین عضو تیم خود را اضافه کنید</div>
<button className="btn primary sm" onClick={() => setCreateOpen(true)}>
<PlusIcon style={{ width: 16 }} /> افزودن پرسنل
</button>
</div>
) : (
<DataTable columns={columns} data={staff} loading={isLoading} />
)}
</div>
{/* Modal ایجاد */}
@@ -187,22 +247,26 @@ function StaffFormFields({ form }: { form: ReturnType<typeof useForm<StaffFormDa
const { register, formState: { errors } } = form;
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<div className="field">
<label>نام و نام خانوادگی *</label>
<input {...register('full_name')} placeholder="علی رضایی" />
{errors.full_name && <span className="field-error">{errors.full_name.message}</span>}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="field">
<label>نام و نام خانوادگی *</label>
<input {...register('full_name')} placeholder="علی رضایی" />
{errors.full_name && <span className="field-error">{errors.full_name.message}</span>}
</div>
<div className="field">
<label>سمت</label>
<input {...register('job_title')} placeholder="پرستار" />
</div>
</div>
<div className="field">
<label>سمت</label>
<input {...register('job_title')} placeholder="پرستار" />
</div>
<div className="field">
<label>تلفن</label>
<input {...register('phone')} placeholder="09121234567" dir="ltr" />
</div>
<div className="field">
<label>کد ملی</label>
<input {...register('national_code')} placeholder="0012345678" dir="ltr" />
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="field">
<label>تلفن</label>
<input {...register('phone')} placeholder="09121234567" dir="ltr" />
</div>
<div className="field">
<label>کد ملی</label>
<input {...register('national_code')} placeholder="0012345678" dir="ltr" />
</div>
</div>
<div className="field">
<label>آدرس</label>