feat: enhance DoctorAddress entity to support clinic addresses and types

- Added `clinic_id` and `type` fields to `DoctorAddress` entity to differentiate between personal and clinic addresses.
- Updated constructor to support creation of addresses for both doctors and clinics.
- Modified repository methods to handle new address types and added methods for counting and finding addresses by clinic.
- Implemented migration to update the database schema accordingly.
- Removed deprecated endpoint for creating addresses from clinics and updated related controller methods.
- Added new endpoints for managing clinic addresses, including CRUD operations.
- Updated frontend components to handle new address types and display accordingly.
This commit is contained in:
hamed
2026-06-12 13:39:37 +03:30
parent 63073c6a42
commit 0333b24071
13 changed files with 1446 additions and 89 deletions
+34 -8
View File
@@ -57,7 +57,11 @@ interface CityOpt { id: number; uuid: string; name: string; }
interface ImageFileData { fid: number; uuid: string; url: string; filename: string; filemime: string; filesize: number; }
interface AddressData {
id: string; uuid: string; name: string | null; address: string | null;
id: string; uuid: string;
type: 'personal' | 'clinic';
clinic_id: string | null;
clinic_name: string | null;
name: string | null; address: string | null;
telephone: string | null;
map: { latitude: string | null; longitude: string | null };
city: { id: string; name: string } | null;
@@ -1061,9 +1065,22 @@ function SessionEditor({ session, onChange, onRemove, addresses }: {
<select value={session.location_id ?? ''}
onChange={e => upd('location_id', e.target.value ? Number(e.target.value) : null)}>
<option value="">انتخاب کنید</option>
{addresses.map(a => (
<option key={a.id} value={a.id}>{a.name ?? a.address ?? `مطب ${a.id}`}</option>
))}
{addresses.filter(a => a.type === 'personal').length > 0 && (
<optgroup label="مطب شخصی">
{addresses.filter(a => a.type === 'personal').map(a => (
<option key={a.id} value={a.id}>{a.name ?? a.address ?? `مطب ${a.id}`}</option>
))}
</optgroup>
)}
{addresses.filter(a => a.type === 'clinic').length > 0 && (
<optgroup label="کلینیک‌ها">
{addresses.filter(a => a.type === 'clinic').map(a => (
<option key={a.id} value={a.id}>
{a.clinic_name ? `${a.clinic_name}${a.name ? `${a.name}` : ''}` : (a.name ?? a.address ?? `کلینیک ${a.id}`)}
</option>
))}
</optgroup>
)}
</select>
</div>
</div>
@@ -1736,8 +1753,17 @@ const SCHEDULE_TABS = [
{ id: 'holidays' as const, label: 'تعطیلات' },
];
function ScheduleSection({ doctorUuid, addresses }: { doctorUuid: string; addresses: AddressData[] }) {
function ScheduleSection({ doctorUuid }: { doctorUuid: string }) {
const [tab, setTab] = useState<'weekly' | 'overrides' | 'holidays'>('weekly');
const locationsQ = useQuery({
queryKey: ['available-locations', doctorUuid],
queryFn: () => api.get<ApiResponse<AddressData[]>>(`/api/v1/appointment-settings/available-locations/${doctorUuid}`),
enabled: !!doctorUuid,
staleTime: 30_000,
});
const availableLocations: AddressData[] = locationsQ.data?.data ?? [];
return (
<div className="cp-card p-6">
<h2 className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-4">برنامه کاری</h2>
@@ -1753,8 +1779,8 @@ function ScheduleSection({ doctorUuid, addresses }: { doctorUuid: string; addres
</button>
))}
</div>
{tab === 'weekly' && <WeeklyScheduleTab doctorUuid={doctorUuid} addresses={addresses} />}
{tab === 'overrides' && <DateOverridesTab doctorUuid={doctorUuid} addresses={addresses} />}
{tab === 'weekly' && <WeeklyScheduleTab doctorUuid={doctorUuid} addresses={availableLocations} />}
{tab === 'overrides' && <DateOverridesTab doctorUuid={doctorUuid} addresses={availableLocations} />}
{tab === 'holidays' && <HolidaysTab doctorUuid={doctorUuid} />}
</div>
);
@@ -2136,7 +2162,7 @@ export default function DoctorDetailPage({ isOwnProfile = false }: { isOwnProfil
)}
</div>
{uuid && <ScheduleSection doctorUuid={uuid} addresses={doctor.address ?? []} />}
{uuid && <ScheduleSection doctorUuid={uuid} />}
{doctor.clinics && doctor.clinics.length > 0 && (
<div className="cp-card p-6">