feat: redesign StaffPage with KPI bar, improved table layout, and enhanced form fields
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user