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
+132 -39
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { PlusIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline';
import { PlusIcon, PencilIcon, TrashIcon, WrenchScrewdriverIcon } from '@heroicons/react/24/outline';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
@@ -129,84 +129,177 @@ export default function ClinicServicesPage() {
<>
<PageHeader title="سرویس‌های کلینیک" description="مدیریت بخش‌ها و سرویس‌های کلینیک" />
<div style={{ display: 'grid', gridTemplateColumns: '280px 1fr', gap: 16, alignItems: 'start' }}>
<div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: 16, alignItems: 'start' }}>
{/* ستون بخش‌ها */}
<div className="card">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '14px 16px', borderBottom: '1px solid var(--border)',
}}>
<b style={{ fontSize: 14 }}>بخشها</b>
<button className="btn primary sm" onClick={() => { sectionForm.reset(); setSectionModal('create'); }}>
<PlusIcon style={{ width: 14 }} />
<button
className="btn primary sm"
style={{ display: 'flex', alignItems: 'center', gap: 4 }}
onClick={() => { sectionForm.reset(); setSectionModal('create'); }}
>
<PlusIcon style={{ width: 14 }} /> بخش جدید
</button>
</div>
{sectionsLoading ? (
<div style={{ color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
) : sections.length === 0 ? (
<div style={{ color: 'var(--text-3)', fontSize: 13 }}>بخشی ثبت نشده است</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{sections.map((s) => (
<div style={{ padding: '8px 0' }}>
{sectionsLoading ? (
<div style={{ color: 'var(--text-3)', fontSize: 13, padding: '12px 16px' }}>در حال بارگذاری...</div>
) : sections.length === 0 ? (
<div style={{ padding: '32px 16px', textAlign: 'center', color: 'var(--text-3)' }}>
<WrenchScrewdriverIcon style={{ width: 32, margin: '0 auto 10px', display: 'block', opacity: 0.4 }} />
<div style={{ fontSize: 13 }}>بخشی ثبت نشده است</div>
</div>
) : (
sections.map((s) => (
<div
key={s.uuid}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '8px 10px', borderRadius: 6, cursor: 'pointer',
padding: '10px 14px', cursor: 'pointer',
background: selectedSection?.uuid === s.uuid ? 'var(--primary-subtle)' : 'transparent',
border: selectedSection?.uuid === s.uuid ? '1px solid var(--primary)' : '1px solid transparent',
borderRight: selectedSection?.uuid === s.uuid ? '3px solid var(--primary)' : '3px solid transparent',
transition: 'all 0.15s',
}}
onClick={() => setSelectedSection(s)}
>
<span style={{ fontSize: 13 }}>{s.name}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<WrenchScrewdriverIcon style={{
width: 16,
color: selectedSection?.uuid === s.uuid ? 'var(--primary)' : 'var(--text-3)',
transition: 'color 0.15s',
}} />
<span style={{
fontSize: 13.5, fontWeight: selectedSection?.uuid === s.uuid ? 600 : 400,
color: selectedSection?.uuid === s.uuid ? 'var(--primary)' : 'var(--text-1)',
}}>
{s.name}
</span>
</div>
<div style={{ display: 'flex', gap: 4 }} onClick={(e) => e.stopPropagation()}>
<button className="btn sm" onClick={() => openEditSection(s)}><PencilIcon style={{ width: 13 }} /></button>
<button className="btn sm" onClick={() => setDeleteSection(s)}><TrashIcon style={{ width: 13 }} /></button>
<button
className="btn sm"
style={{ opacity: 0.7 }}
onClick={() => openEditSection(s)}
title="ویرایش"
>
<PencilIcon style={{ width: 13 }} />
</button>
<button
className="btn sm"
style={{ opacity: 0.7 }}
onClick={() => setDeleteSection(s)}
title="حذف"
>
<TrashIcon style={{ width: 13 }} />
</button>
</div>
</div>
))}
</div>
)}
))
)}
</div>
</div>
{/* ستون آیتم‌ها */}
<div className="card">
{!selectedSection ? (
<div style={{ padding: '40px 0', textAlign: 'center', color: 'var(--text-3)' }}>
یک بخش را از سمت راست انتخاب کنید
<div style={{
border: '2px dashed var(--border)', borderRadius: 10,
margin: 16, padding: '60px 0', textAlign: 'center', color: 'var(--text-3)',
}}>
<WrenchScrewdriverIcon style={{ width: 40, margin: '0 auto 14px', display: 'block', opacity: 0.35 }} />
<div style={{ fontSize: 14, fontWeight: 500, color: 'var(--text-2)' }}>یک بخش انتخاب کنید</div>
<div style={{ fontSize: 13, marginTop: 4 }}>تا سرویسهای آن را مشاهده و مدیریت کنید</div>
</div>
) : (
<>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
<b style={{ fontSize: 14 }}>سرویسهای «{selectedSection.name}»</b>
{/* toolbar بخش انتخاب‌شده */}
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '14px 16px', borderBottom: '1px solid var(--border)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<WrenchScrewdriverIcon style={{ width: 16, color: 'var(--primary)' }} />
<b style={{ fontSize: 14 }}>{selectedSection.name}</b>
{!itemsLoading && (
<span className="badge gray" style={{ fontSize: 11 }}>{items.length} سرویس</span>
)}
</div>
<button
className="btn primary sm"
style={{ display: 'flex', alignItems: 'center', gap: 4 }}
onClick={() => { itemForm.reset({ price_rials: 0, staff_uuid: '' }); setItemModal('create'); }}
>
<PlusIcon style={{ width: 14 }} /> سرویس جدید
</button>
</div>
{itemsLoading ? (
<div style={{ color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
<div style={{ color: 'var(--text-3)', fontSize: 13, padding: '16px 16px' }}>در حال بارگذاری...</div>
) : items.length === 0 ? (
<div style={{ color: 'var(--text-3)', fontSize: 13 }}>سرویسی ثبت نشده است</div>
<div style={{ padding: '48px 0', textAlign: 'center', color: 'var(--text-3)' }}>
<PlusIcon style={{ width: 32, margin: '0 auto 12px', display: 'block', opacity: 0.35 }} />
<div style={{ fontWeight: 500, color: 'var(--text-2)', marginBottom: 4 }}>سرویسی در این بخش وجود ندارد</div>
<button
className="btn primary sm"
style={{ marginTop: 12 }}
onClick={() => { itemForm.reset({ price_rials: 0, staff_uuid: '' }); setItemModal('create'); }}
>
افزودن سرویس
</button>
</div>
) : (
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--border)' }}>
<th style={{ textAlign: 'right', padding: '8px 4px' }}>نام سرویس</th>
<th style={{ textAlign: 'right', padding: '8px 4px' }}>قیمت</th>
<th style={{ textAlign: 'right', padding: '8px 4px' }}>پرسنل مسئول</th>
<th style={{ textAlign: 'right', padding: '8px 4px' }}>عملیات</th>
<tr style={{ background: 'oklch(0.97 0.01 256)', borderBottom: '1px solid var(--border)' }}>
<th style={{ textAlign: 'right', padding: '10px 16px', fontWeight: 600, color: 'var(--text-2)' }}>نام سرویس</th>
<th style={{ textAlign: 'right', padding: '10px 16px', fontWeight: 600, color: 'var(--text-2)' }}>قیمت</th>
<th style={{ textAlign: 'right', padding: '10px 16px', fontWeight: 600, color: 'var(--text-2)' }}>پرسنل مسئول</th>
<th style={{ textAlign: 'right', padding: '10px 16px', fontWeight: 600, color: 'var(--text-2)' }}>عملیات</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.uuid} style={{ borderBottom: '1px solid var(--border)' }}>
<td style={{ padding: '8px 4px' }}>{item.name}</td>
<td style={{ padding: '8px 4px' }}>{formatRial(item.price_rials)}</td>
<td style={{ padding: '8px 4px' }}>{item.staff?.full_name ?? '—'}</td>
<td style={{ padding: '8px 4px' }}>
{items.map((item, idx) => (
<tr
key={item.uuid}
style={{
borderBottom: '1px solid var(--border)',
background: idx % 2 === 1 ? 'oklch(0.985 0.005 256)' : 'transparent',
}}
>
<td style={{ padding: '11px 16px', fontWeight: 500 }}>{item.name}</td>
<td style={{ padding: '11px 16px', color: 'var(--primary)', fontWeight: 600 }}>
{formatRial(item.price_rials)}
</td>
<td style={{ padding: '11px 16px' }}>
{item.staff ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div style={{
width: 22, height: 22, borderRadius: '50%',
background: 'linear-gradient(145deg, oklch(0.62 0.15 162), oklch(0.48 0.16 162))',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 10, fontWeight: 700, color: '#fff', flexShrink: 0,
}}>
{item.staff.full_name.charAt(0)}
</div>
<span style={{ fontSize: 13 }}>{item.staff.full_name}</span>
</div>
) : (
<span style={{ color: 'var(--text-3)' }}></span>
)}
</td>
<td style={{ padding: '11px 16px' }}>
<div style={{ display: 'flex', gap: 4 }}>
<button className="btn sm" onClick={() => openEditItem(item)}><PencilIcon style={{ width: 13 }} /></button>
<button className="btn sm" onClick={() => setDeleteItem(item)}><TrashIcon style={{ width: 13 }} /></button>
<button className="btn sm" onClick={() => openEditItem(item)} title="ویرایش">
<PencilIcon style={{ width: 13 }} />
</button>
<button className="btn sm" onClick={() => setDeleteItem(item)} title="حذف">
<TrashIcon style={{ width: 13 }} />
</button>
</div>
</td>
</tr>