add HospitalNetwork management page and service integration

This commit is contained in:
2026-09-02 18:20:57 +05:30
parent 4873b360fc
commit 78679fbe72
3 changed files with 1540 additions and 528 deletions
File diff suppressed because it is too large Load Diff
+538 -99
View File
@@ -47,9 +47,13 @@ export default function HospitalNetwork({ notify }) {
return saved ? JSON.parse(saved) : DEFAULT_STAFF return saved ? JSON.parse(saved) : DEFAULT_STAFF
}) })
// Providers (Doctors) fetched from real API per hospital
const [providers, setProviders] = useState([])
const [providersLoading, setProvidersLoading] = useState(false)
// Selection & UI States // Selection & UI States
const [selectedGroupId, setSelectedGroupId] = useState('HG001') const [selectedGroupId, setSelectedGroupId] = useState('HG001')
const [activeHospitalStaffId, setActiveHospitalStaffId] = useState(null) // ID of hospital whose staff modal is open const [activeHospitalStaffId, setActiveHospitalStaffId] = useState(null)
const [staffTab, setStaffTab] = useState('doctor') // 'doctor' | 'caregiver' const [staffTab, setStaffTab] = useState('doctor') // 'doctor' | 'caregiver'
// Searches & Filters // Searches & Filters
@@ -90,7 +94,17 @@ export default function HospitalNetwork({ notify }) {
adminPassword: '', adminPassword: '',
adminUsername: '', adminUsername: '',
}) })
const [staffForm, setStaffForm] = useState({ type: 'doctor', name: '', specialty: '', role: 'Registered Nurse', experience: '1 Yr', certification: '', status: 'On Duty', phone: '', email: '' }) const [staffForm, setStaffForm] = useState({
type: 'doctor',
// Doctor-specific (API provider fields)
firstName: '', lastName: '', email: '', username: '', password: '',
mobile: '', isdCode: '+91', gender: 'male', dob: '', bloodGroup: '',
licenseNo: '', practicingSince: '',
providerSpeciality: '', // comma-separated input → array on submit
// Caregiver-specific (local only)
name: '', specialty: '', role: 'Registered Nurse', experience: '1 Yr',
certification: '', status: 'On Duty', phone: '',
})
// Synchronize with LocalStorage // Synchronize with LocalStorage
useEffect(() => { useEffect(() => {
@@ -119,8 +133,6 @@ export default function HospitalNetwork({ notify }) {
fetchedGroups = groupsRes fetchedGroups = groupsRes
} }
// Normalize backend shape → frontend shape
// Backend uses additional_info.city for HQ, reg_no as identifier, no established/description fields
fetchedGroups = fetchedGroups.map(g => { fetchedGroups = fetchedGroups.map(g => {
const info = g.additional_info const info = g.additional_info
? (typeof g.additional_info === 'string' ? JSON.parse(g.additional_info) : g.additional_info) ? (typeof g.additional_info === 'string' ? JSON.parse(g.additional_info) : g.additional_info)
@@ -135,7 +147,6 @@ export default function HospitalNetwork({ notify }) {
if (fetchedGroups.length > 0) { if (fetchedGroups.length > 0) {
setGroups(fetchedGroups) setGroups(fetchedGroups)
// Automatically select the first fetched group ID
setSelectedGroupId(fetchedGroups[0].id) setSelectedGroupId(fetchedGroups[0].id)
} }
@@ -173,7 +184,7 @@ export default function HospitalNetwork({ notify }) {
console.log('Hospitals API bypass') console.log('Hospitals API bypass')
} }
// Fetch staff // Fetch caregiver staff
try { try {
const staffRes = await adminService.hospitalNetwork.getStaff() const staffRes = await adminService.hospitalNetwork.getStaff()
let fetchedStaff = [] let fetchedStaff = []
@@ -196,10 +207,41 @@ export default function HospitalNetwork({ notify }) {
} }
}, []) }, [])
// Fetch providers (doctors) for a specific hospital from the real API
const fetchProviders = useCallback(async (hospitalId) => {
if (!hospitalId) return
setProvidersLoading(true)
setProviders([])
try {
const res = await adminService.hospitalNetwork.getProviders(hospitalId)
// Response shape: { success, data: { providers: [...], pagination: {...} } }
let list = []
if (res?.data?.providers && Array.isArray(res.data.providers)) {
list = res.data.providers
} else if (res?.data && Array.isArray(res.data)) {
list = res.data
} else if (Array.isArray(res)) {
list = res
}
setProviders(list)
} catch (err) {
console.log('Providers fetch failed, showing local doctors')
setProviders([])
} finally {
setProvidersLoading(false)
}
}, [])
useEffect(() => { useEffect(() => {
syncWithApi() syncWithApi()
}, [syncWithApi]) }, [syncWithApi])
useEffect(() => {
if (activeHospitalStaffId) {
fetchProviders(activeHospitalStaffId)
}
}, [activeHospitalStaffId, fetchProviders])
// Analytics Helpers // Analytics Helpers
const analytics = useMemo(() => { const analytics = useMemo(() => {
const totalGroups = groups.length const totalGroups = groups.length
@@ -230,16 +272,27 @@ export default function HospitalNetwork({ notify }) {
}, [groups, groupQuery]) }, [groups, groupQuery])
const selectedGroup = useMemo(() => { const selectedGroup = useMemo(() => {
if (selectedGroupId === 'standalone') {
return {
id: 'standalone',
name: 'Independent Facilities',
description: 'Standalone hospitals and clinic networks operating independently of corporate provider groups.'
}
}
return groups.find(g => g.id === selectedGroupId) || groups[0] return groups.find(g => g.id === selectedGroupId) || groups[0]
}, [groups, selectedGroupId]) }, [groups, selectedGroupId])
const filteredHospitals = useMemo(() => { const filteredHospitals = useMemo(() => {
if (!selectedGroupId) return [] if (!selectedGroupId) return []
return hospitals.filter(h => return hospitals.filter(h => {
(h.groupId === selectedGroupId || h.group_id === selectedGroupId || String(h.groupId) === String(selectedGroupId) || String(h.group_id) === String(selectedGroupId)) && const matchesGroup = selectedGroupId === 'standalone'
? (!h.groupId && !h.group_id)
: (h.groupId === selectedGroupId || h.group_id === selectedGroupId || String(h.groupId) === String(selectedGroupId) || String(h.group_id) === String(selectedGroupId));
return matchesGroup &&
(h.name.toLowerCase().includes(hospitalQuery.toLowerCase()) || (h.name.toLowerCase().includes(hospitalQuery.toLowerCase()) ||
h.city.toLowerCase().includes(hospitalQuery.toLowerCase())) (h.city || '').toLowerCase().includes(hospitalQuery.toLowerCase()))
) })
}, [hospitals, selectedGroupId, hospitalQuery]) }, [hospitals, selectedGroupId, hospitalQuery])
const activeHospital = useMemo(() => { const activeHospital = useMemo(() => {
@@ -265,11 +318,39 @@ export default function HospitalNetwork({ notify }) {
}) })
}, [staff, activeHospitalStaffId, staffTab, staffQuery, specialtyFilter, roleFilter]) }, [staff, activeHospitalStaffId, staffTab, staffQuery, specialtyFilter, roleFilter])
// Unique list of providers (doctors) filtered from the API response
const filteredProviders = useMemo(() => {
if (staffTab !== 'doctor') return []
return providers.filter(p => {
const user = p.user || {}
const fullName = `${user.first_name || ''} ${user.last_name || ''}`.toLowerCase()
const email = (user.email || '').toLowerCase()
const username = (user.username || '').toLowerCase()
const matchesSearch = fullName.includes(staffQuery.toLowerCase()) ||
email.includes(staffQuery.toLowerCase()) ||
username.includes(staffQuery.toLowerCase())
const specs = p.provider_speciality || []
const specialtyNames = specs.map(s => s.speciality || '').filter(Boolean)
const matchesSpec = specialtyFilter === 'All' ||
specialtyNames.some(sName => sName.toLowerCase() === specialtyFilter.toLowerCase())
return matchesSearch && matchesSpec
})
}, [providers, staffTab, staffQuery, specialtyFilter])
// Unique lists for filtering dropdowns // Unique lists for filtering dropdowns
const specialties = useMemo(() => { const specialties = useMemo(() => {
const list = staff.filter(s => s.type === 'doctor').map(s => s.specialty) const list = staff.filter(s => s.type === 'doctor').map(s => s.specialty)
return ['All', ...new Set(list)] providers.forEach(p => {
}, [staff]) if (p.provider_speciality && Array.isArray(p.provider_speciality)) {
p.provider_speciality.forEach(s => {
if (s.speciality) list.push(s.speciality)
})
}
})
return ['All', ...new Set(list.filter(Boolean))]
}, [staff, providers])
const roles = useMemo(() => { const roles = useMemo(() => {
const list = staff.filter(s => s.type === 'caregiver').map(s => s.role) const list = staff.filter(s => s.type === 'caregiver').map(s => s.role)
@@ -516,19 +597,40 @@ export default function HospitalNetwork({ notify }) {
setEditItem(item) setEditItem(item)
setStaffForm({ setStaffForm({
type: item.type, type: item.type,
name: item.name, // Doctor fields
firstName: item.user?.first_name || item.firstName || '',
lastName: item.user?.last_name || item.lastName || '',
email: item.user?.email || item.email || '',
username: item.user?.username || item.username || '',
password: '',
mobile: item.user?.mobile || item.mobile || '',
isdCode: item.user?.isd_code || item.isdCode || '+91',
gender: item.user?.gender || item.gender || 'male',
dob: item.user?.dob || item.dob || '',
bloodGroup: item.user?.blood_group || item.bloodGroup || '',
licenseNo: item.license_no || item.licenseNo || '',
practicingSince: item.practicing_since || item.practicingSince || '',
providerSpeciality: Array.isArray(item.provider_speciality)
? item.provider_speciality.join(', ')
: (item.providerSpeciality || item.specialty || ''),
// Caregiver fields
name: item.name || '',
specialty: item.specialty || '', specialty: item.specialty || '',
role: item.role || 'Registered Nurse', role: item.role || 'Registered Nurse',
experience: item.experience || '1 Yr', experience: item.experience || '1 Yr',
certification: item.certification || '', certification: item.certification || '',
status: item.status, status: item.status || 'On Duty',
phone: item.phone, phone: item.phone || '',
email: item.email
}) })
} else { } else {
setEditItem(null) setEditItem(null)
setStaffForm({ setStaffForm({
type: staffTab, type: staffTab,
// Doctor fields
firstName: '', lastName: '', email: '', username: '', password: '',
mobile: '', isdCode: '+91', gender: 'male', dob: '', bloodGroup: '',
licenseNo: '', practicingSince: '', providerSpeciality: '',
// Caregiver fields
name: '', name: '',
specialty: '', specialty: '',
role: 'Registered Nurse', role: 'Registered Nurse',
@@ -536,30 +638,95 @@ export default function HospitalNetwork({ notify }) {
certification: '', certification: '',
status: staffTab === 'doctor' ? 'On Duty' : 'Active', status: staffTab === 'doctor' ? 'On Duty' : 'Active',
phone: '', phone: '',
email: ''
}) })
} }
} }
const handleStaffSubmit = async (e) => { const handleStaffSubmit = async (e) => {
e.preventDefault() e.preventDefault()
if (!staffForm.name.trim()) return notify('Provider Name is required', true)
// ── DOCTOR: call the real provider API ──
if (staffForm.type === 'doctor') {
if (!staffForm.firstName.trim()) return notify('First Name is required', true)
if (!staffForm.lastName.trim()) return notify('Last Name is required', true)
if (!staffForm.email.trim()) return notify('Email is required', true)
if (modalMode === 'create' && !staffForm.password.trim()) return notify('Password is required', true)
if (!staffForm.mobile.trim()) return notify('Mobile number is required', true)
if (!staffForm.licenseNo.trim()) return notify('License number is required', true)
try {
const providerPayload = {
user: {
role: 'provider',
first_name: staffForm.firstName,
last_name: staffForm.lastName,
email: staffForm.email,
username: staffForm.username || `${staffForm.firstName.toLowerCase()}_${staffForm.lastName.toLowerCase()}`,
password: staffForm.password,
mobile: staffForm.mobile,
isd_code: staffForm.isdCode,
gender: staffForm.gender,
dob: staffForm.dob || '1980-01-01',
blood_group: staffForm.bloodGroup || 'O+',
timezone_id: 1,
country_iso: 'IN',
is_active: 1,
},
license_no: staffForm.licenseNo,
practicing_since: staffForm.practicingSince || '2010-01-01',
provider_speciality: staffForm.providerSpeciality
? staffForm.providerSpeciality.split(',').map(s => s.trim()).filter(Boolean)
: [],
}
const res = await adminService.hospitalNetwork.addProvider(activeHospitalStaffId, providerPayload)
if (res?.success || res?.status === 201 || res?.data) {
notify(`Dr. ${staffForm.firstName} ${staffForm.lastName} registered successfully.`)
// Add optimistic local entry so table updates immediately
const localEntry = {
id: res?.data?.id || ('S' + Date.now().toString().slice(-4)),
hospitalId: activeHospitalStaffId,
hospital_id: activeHospitalStaffId,
type: 'doctor',
name: `Dr. ${staffForm.firstName} ${staffForm.lastName}`,
specialty: staffForm.providerSpeciality.split(',')[0]?.trim() || 'General Physician',
experience: staffForm.practicingSince
? `${new Date().getFullYear() - new Date(staffForm.practicingSince).getFullYear()} Yrs`
: '1 Yr',
status: 'On Duty',
phone: `${staffForm.isdCode}${staffForm.mobile}`,
email: staffForm.email,
}
setStaff(prev => [...prev, localEntry])
setModalType(null)
} else {
notify(res?.message || 'Failed to register provider', true)
}
} catch (err) {
notify(err.message || 'An error occurred while registering provider', true)
}
return
}
// ── CAREGIVER: local state only ──
if (!staffForm.name.trim()) return notify('Caregiver Name is required', true)
if (modalMode === 'create') { if (modalMode === 'create') {
const newStaff = { const newStaff = {
id: 'S' + Date.now().toString().slice(-4), id: 'S' + Date.now().toString().slice(-4),
hospitalId: activeHospitalStaffId, hospitalId: activeHospitalStaffId,
type: staffForm.type, hospital_id: activeHospitalStaffId,
type: 'caregiver',
name: staffForm.name, name: staffForm.name,
status: staffForm.status, status: staffForm.status,
phone: staffForm.phone, phone: staffForm.phone,
email: staffForm.email, email: staffForm.email || '',
...(staffForm.type === 'doctor' role: staffForm.role,
? { specialty: staffForm.specialty || 'General Physician', experience: staffForm.experience } certification: staffForm.certification,
: { role: staffForm.role, certification: staffForm.certification })
} }
setStaff(prev => [...prev, newStaff]) setStaff(prev => [...prev, newStaff])
notify(`${staffForm.type === 'doctor' ? 'Doctor' : 'Caregiver'} "${staffForm.name}" registered successfully.`) notify(`Caregiver "${staffForm.name}" registered successfully.`)
} else if (modalMode === 'edit' && editItem) { } else if (modalMode === 'edit' && editItem) {
setStaff(prev => prev.map(s => s.id === editItem.id ? { setStaff(prev => prev.map(s => s.id === editItem.id ? {
...s, ...s,
@@ -647,8 +814,7 @@ export default function HospitalNetwork({ notify }) {
<div className="network-left-sidebar"> <div className="network-left-sidebar">
<div className="pane-header"> <div className="pane-header">
<div className="header-search-row"> <div className="header-search-row">
<h3 className="section-pane-title">Hospital Groups</h3> <h3 className="section-pane-title">Healthcare Network</h3> <button className="add-mini-btn" title="Create New Hospital Group" onClick={() => handleOpenGroupModal('create')}>
<button className="add-mini-btn" title="Create New Hospital Group" onClick={() => handleOpenGroupModal('create')}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="12" y1="5" x2="12" y2="19" /> <line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" /> <line x1="5" y1="12" x2="19" y2="12" />
@@ -670,46 +836,97 @@ export default function HospitalNetwork({ notify }) {
</div> </div>
<div className="groups-list"> <div className="groups-list">
{/* Hospital Groups Section */}
<div className="network-section-label">
<span>HOSPITAL GROUPS</span>
<span>{groups.length}</span>
</div>
{/* Hospital Groups */}
{filteredGroups.length === 0 ? ( {filteredGroups.length === 0 ? (
<div className="empty-state">No hospital groups found.</div> groupQuery && (
<div className="empty-state">
No hospital groups found.
</div>
)
) : ( ) : (
filteredGroups.map(g => { filteredGroups.map(g => {
const isSelected = g.id === selectedGroupId const isSelected = g.id === selectedGroupId
const hospitalCount = hospitals.filter(h => h.groupId === g.id || h.group_id === g.id || String(h.groupId) === String(g.id) || String(h.group_id) === String(g.id)).length
// Safely extract hq and established year from backend-specific keys if missing const hospitalCount = hospitals.filter(
h =>
h.groupId === g.id ||
h.group_id === g.id ||
String(h.groupId) === String(g.id) ||
String(h.group_id) === String(g.id)
).length
let hqVal = g.hq let hqVal = g.hq
let estVal = g.established let estVal = g.established
if (!hqVal && g.additional_info) { if (!hqVal && g.additional_info) {
try { try {
const info = typeof g.additional_info === 'string' ? JSON.parse(g.additional_info) : g.additional_info const info =
typeof g.additional_info === 'string'
? JSON.parse(g.additional_info)
: g.additional_info
hqVal = info.city || info.hq hqVal = info.city || info.hq
} catch (e) { } catch (e) {
hqVal = 'N/A' hqVal = 'N/A'
} }
} }
if (!estVal && g.created_at) { if (!estVal && g.created_at) {
estVal = g.created_at.substring(0, 4) estVal = g.created_at.substring(0, 4)
} }
hqVal = hqVal || 'N/A' hqVal = hqVal || 'N/A'
estVal = estVal || 'N/A' estVal = estVal || 'N/A'
return ( return (
<div <div
key={g.id} key={g.id}
className={`group-item-card ${isSelected ? 'selected' : ''}`} className={`group-item-card ${
isSelected ? 'selected' : ''
}`}
onClick={() => setSelectedGroupId(g.id)} onClick={() => setSelectedGroupId(g.id)}
> >
<div className="group-item-header"> <div className="group-item-header">
<h4 className="group-name">{g.name}</h4> <h4 className="group-name">
<span className="group-badge">{hospitalCount} facilities</span> {g.name}
</h4>
<span className="group-badge">
{hospitalCount} hospital
</span>
</div> </div>
<p className="group-hq">HQ: {hqVal} · Est: {estVal}</p>
<p className="group-hq">
HQ: {hqVal} · Est: {estVal}
</p>
<div className="group-actions-inline"> <div className="group-actions-inline">
<button className="icon-link-btn" title="Edit Group" onClick={(e) => { e.stopPropagation(); handleOpenGroupModal('edit', g); }}> <button
className="icon-link-btn"
title="Edit Group"
onClick={(e) => {
e.stopPropagation()
handleOpenGroupModal('edit', g)
}}
>
Edit Edit
</button> </button>
<button className="icon-link-btn danger-link" title="Delete Group" onClick={(e) => { e.stopPropagation(); handleDeleteGroup(g.id, g.name); }}>
<button
className="icon-link-btn danger-link"
title="Delete Group"
onClick={(e) => {
e.stopPropagation()
handleDeleteGroup(g.id, g.name)
}}
>
Delete Delete
</button> </button>
</div> </div>
@@ -717,6 +934,66 @@ export default function HospitalNetwork({ notify }) {
) )
}) })
)} )}
{/* Independent Facilities Section */}
<div className="network-section-label standalone-section-label">
<span>STANDALONE HOSPITALS</span>
<span>
{
hospitals.filter(
h => !h.groupId && !h.group_id
).length
}
</span>
</div>
{/* Standalone Hospitals Card */}
{(!groupQuery ||
'standalone hospitals'.includes(groupQuery.toLowerCase()) ||
'standalone'.includes(groupQuery.toLowerCase())) && (
<div
className={`group-item-card standalone-card ${
selectedGroupId === 'standalone'
? 'selected'
: ''
}`}
onClick={() => setSelectedGroupId('standalone')}
>
<div className="group-item-header">
<h4
className="group-name"
style={{
color:
selectedGroupId === 'standalone'
? '#a855f7'
: 'inherit'
}}
>
Standalone Hospitals
</h4>
<span className="group-badge">
{
hospitals.filter(
h => !h.groupId && !h.group_id
).length
} hospitals
</span>
</div>
<p
className="group-hq"
style={{
fontSize: '0.85rem',
opacity: 0.75
}}
>
Hospitals not affiliated with a hospital group
</p>
</div>
)}
</div> </div>
</div> </div>
@@ -799,7 +1076,7 @@ export default function HospitalNetwork({ notify }) {
<button className="mini-action-btn danger" title="Remove Facility" onClick={() => handleDeleteHospital(h.id, h.name)}> <button className="mini-action-btn danger" title="Remove Facility" onClick={() => handleDeleteHospital(h.id, h.name)}>
Delete Delete
</button> </button>
<button className="action-btn-primary" onClick={() => { setActiveHospitalStaffId(h.id); setStaffTab('doctor'); }}> <button className="manage-staff-btn" onClick={() => { setActiveHospitalStaffId(h.id); setStaffTab('doctor'); }}>
Manage Staff Manage Staff
</button> </button>
</div> </div>
@@ -839,7 +1116,7 @@ export default function HospitalNetwork({ notify }) {
className={`roster-tab-btn ${staffTab === 'doctor' ? 'active' : ''}`} className={`roster-tab-btn ${staffTab === 'doctor' ? 'active' : ''}`}
onClick={() => { setStaffTab('doctor'); setStaffQuery(''); }} onClick={() => { setStaffTab('doctor'); setStaffQuery(''); }}
> >
Medical Doctors ({hospitalStaffStats[activeHospital.id]?.doctors || 0}) Medical Doctors ({providers.length})
</button> </button>
<button <button
className={`roster-tab-btn ${staffTab === 'caregiver' ? 'active' : ''}`} className={`roster-tab-btn ${staffTab === 'caregiver' ? 'active' : ''}`}
@@ -893,20 +1170,61 @@ export default function HospitalNetwork({ notify }) {
</div> </div>
</div> </div>
{/* Roster Table */} {/* Doctors (API-backed cards grid) or Caregivers (local table) */}
{staffTab === 'doctor' ? (
providersLoading ? (
<div className="roster-loading">
<div className="roster-spinner"></div>
<span>Loading registered providers...</span>
</div>
) : filteredProviders.length === 0 ? (
<div className="empty-state-full" style={{ padding: '32px' }}>
<p>No matching doctors found at this facility.</p>
</div>
) : (
<div className="providers-cards-grid">
{filteredProviders.map(p => {
const user = p.user || {}
const name = `Dr. ${user.first_name || ''} ${user.last_name || ''}`
const initial = user.first_name ? user.first_name[0].toUpperCase() : 'D'
const specs = p.provider_speciality || []
const specialtyText = specs.map(s => s.speciality).join(', ') || 'General Physician'
return (
<div key={p.id} className="provider-card">
<div className="provider-card-header">
<div className="provider-avatar">
{initial}
</div>
<div className="provider-card-meta">
<h4 className="provider-name">{name}</h4>
<p className="provider-email">{user.email || 'N/A'}</p>
</div>
</div>
<div className="provider-card-body">
<span className="provider-tag tag-specialty">{specialtyText}</span>
<span className="provider-tag tag-license">Lic: {p.license_no}</span>
<span className="provider-tag tag-since">Since: {p.practicing_since || 'N/A'}</span>
{user.mobile && (
<span className="provider-tag tag-mobile">📞 {user.isd_code || ''} {user.mobile}</span>
)}
</div>
<div className="provider-card-footer">
<span className="provider-status-dot">Active</span>
<div className="provider-actions">
<button className="mini-action-btn" onClick={() => handleOpenStaffModal('edit', p)}>Edit</button>
<button className="mini-action-btn danger" onClick={() => handleDeleteStaff(p.id, name)}>Remove</button>
</div>
</div>
</div>
)
})}
</div>
)
) : (
<div className="table-responsive roster-table-container"> <div className="table-responsive roster-table-container">
<table className="telemetry-table"> <table className="telemetry-table">
<thead> <thead>
{staffTab === 'doctor' ? (
<tr>
<th>Clinician Name</th>
<th>Specialty</th>
<th>Experience</th>
<th>Contact Number</th>
<th>Operations Status</th>
<th>Actions</th>
</tr>
) : (
<tr> <tr>
<th>Caregiver Name</th> <th>Caregiver Name</th>
<th>Role category</th> <th>Role category</th>
@@ -915,7 +1233,6 @@ export default function HospitalNetwork({ notify }) {
<th>Work Status</th> <th>Work Status</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
)}
</thead> </thead>
<tbody> <tbody>
{filteredStaff.length === 0 ? ( {filteredStaff.length === 0 ? (
@@ -932,7 +1249,7 @@ export default function HospitalNetwork({ notify }) {
<td> <td>
<div className="staff-avatar-cell"> <div className="staff-avatar-cell">
<div className="cg-avatar mini-av"> <div className="cg-avatar mini-av">
{s.type === 'doctor' ? 'DR' : (s.role ? s.role.substring(0, 2).toUpperCase() : 'CG')} {s.role ? s.role.substring(0, 2).toUpperCase() : 'CG'}
</div> </div>
<div className="staff-meta-details"> <div className="staff-meta-details">
<strong className="staff-cell-name">{s.name}</strong> <strong className="staff-cell-name">{s.name}</strong>
@@ -940,8 +1257,8 @@ export default function HospitalNetwork({ notify }) {
</div> </div>
</div> </div>
</td> </td>
<td>{s.type === 'doctor' ? s.specialty : s.role}</td> <td>{s.role}</td>
<td>{s.type === 'doctor' ? s.experience : (s.certification || 'Verified')}</td> <td>{s.certification || 'Verified'}</td>
<td>{s.phone || 'N/A'}</td> <td>{s.phone || 'N/A'}</td>
<td> <td>
<span className={`status-indicator-tag status-${statusClass}`}> <span className={`status-indicator-tag status-${statusClass}`}>
@@ -961,6 +1278,7 @@ export default function HospitalNetwork({ notify }) {
</tbody> </tbody>
</table> </table>
</div> </div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -1284,53 +1602,183 @@ export default function HospitalNetwork({ notify }) {
{/* 3. Staff Form */} {/* 3. Staff Form */}
{modalType === 'staff' && ( {modalType === 'staff' && (
<form onSubmit={handleStaffSubmit} className="modal-form"> <form onSubmit={handleStaffSubmit} className={`modal-form ${staffForm.type === 'doctor' ? 'form-grid-two-col' : ''}`}>
<div className="form-row-double">
<div className="form-group"> {/* Roster type selector — always full width */}
<div className="form-group" style={{ gridColumn: staffForm.type === 'doctor' ? '1 / -1' : undefined }}>
<label>Roster Type</label> <label>Roster Type</label>
<select <select
disabled={modalMode === 'edit'} disabled={modalMode === 'edit'}
value={staffForm.type} value={staffForm.type}
onChange={(e) => setStaffForm(prev => ({ ...prev, type: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, type: e.target.value }))}
> >
<option value="doctor">Medical Doctor</option> <option value="doctor">Medical Doctor (Provider)</option>
<option value="caregiver">Caregiver / Nurse</option> <option value="caregiver">Caregiver / Nurse</option>
</select> </select>
</div> </div>
{/* ── DOCTOR FORM (two-column, API-backed) ── */}
{staffForm.type === 'doctor' ? (
<>
{/* Left Column */}
<div className="form-column">
<h4 className="form-section-title">Personal Information</h4>
<div className="form-row-double">
<div className="form-group"> <div className="form-group">
<label>Full Legal Name *</label> <label>First Name *</label>
<input <input
type="text" type="text" required
required placeholder="e.g., Arjun"
placeholder="e.g., Dr. Robert Chen" value={staffForm.firstName}
value={staffForm.name} onChange={(e) => setStaffForm(prev => ({ ...prev, firstName: e.target.value }))}
onChange={(e) => setStaffForm(prev => ({ ...prev, name: e.target.value }))} />
</div>
<div className="form-group">
<label>Last Name *</label>
<input
type="text" required
placeholder="e.g., Patel"
value={staffForm.lastName}
onChange={(e) => setStaffForm(prev => ({ ...prev, lastName: e.target.value }))}
/> />
</div> </div>
</div> </div>
{staffForm.type === 'doctor' ? ( <div className="form-group">
<label>Email Address *</label>
<input
type="email" required
placeholder="e.g., arjun.patel@healthplus.com"
value={staffForm.email}
onChange={(e) => setStaffForm(prev => ({ ...prev, email: e.target.value }))}
/>
</div>
<div className="form-row-double"> <div className="form-row-double">
<div className="form-group"> <div className="form-group">
<label>Specialty Department</label> <label>ISD Code</label>
<input <input
type="text" type="text"
placeholder="e.g., Oncologist" placeholder="e.g., +91"
value={staffForm.specialty} value={staffForm.isdCode}
onChange={(e) => setStaffForm(prev => ({ ...prev, specialty: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, isdCode: e.target.value }))}
/> />
</div> </div>
<div className="form-group"> <div className="form-group">
<label>Clinical Experience</label> <label>Mobile Number *</label>
<input <input
type="text" type="text" required
placeholder="e.g., 10 Yrs" placeholder="e.g., 9123456789"
value={staffForm.experience} value={staffForm.mobile}
onChange={(e) => setStaffForm(prev => ({ ...prev, experience: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, mobile: e.target.value }))}
/> />
</div> </div>
</div> </div>
<div className="form-row-double">
<div className="form-group">
<label>Gender</label>
<select
value={staffForm.gender}
onChange={(e) => setStaffForm(prev => ({ ...prev, gender: e.target.value }))}
>
<option value="male">Male</option>
<option value="female">Female</option>
<option value="other">Other</option>
</select>
</div>
<div className="form-group">
<label>Date of Birth</label>
<input
type="date"
value={staffForm.dob}
onChange={(e) => setStaffForm(prev => ({ ...prev, dob: e.target.value }))}
/>
</div>
</div>
<div className="form-group">
<label>Blood Group</label>
<select
value={staffForm.bloodGroup}
onChange={(e) => setStaffForm(prev => ({ ...prev, bloodGroup: e.target.value }))}
>
<option value="">— Select —</option>
{['A+','A-','B+','B-','AB+','AB-','O+','O-'].map(bg => (
<option key={bg} value={bg}>{bg}</option>
))}
</select>
</div>
</div>
{/* Right Column */}
<div className="form-column">
<h4 className="form-section-title">Credentials & Account</h4>
<div className="form-group">
<label>Username *</label>
<input
type="text" required
placeholder="e.g., dr_arjunpatel"
value={staffForm.username}
onChange={(e) => setStaffForm(prev => ({ ...prev, username: e.target.value }))}
/>
</div>
<div className="form-group">
<label>Password {modalMode === 'create' ? '*' : '(leave blank to keep current)'}</label>
<input
type="password"
required={modalMode === 'create'}
placeholder="e.g., HealthPlus@2026"
value={staffForm.password}
onChange={(e) => setStaffForm(prev => ({ ...prev, password: e.target.value }))}
/>
</div>
<div className="form-group">
<label>Medical License No. *</label>
<input
type="text" required
placeholder="e.g., LIC98765432"
value={staffForm.licenseNo}
onChange={(e) => setStaffForm(prev => ({ ...prev, licenseNo: e.target.value }))}
/>
</div>
<div className="form-group">
<label>Practicing Since</label>
<input
type="date"
value={staffForm.practicingSince}
onChange={(e) => setStaffForm(prev => ({ ...prev, practicingSince: e.target.value }))}
/>
</div>
<div className="form-group">
<label>Specialities (comma-separated) *</label>
<input
type="text" required
placeholder="e.g., Cardiology, Internal Medicine"
value={staffForm.providerSpeciality}
onChange={(e) => setStaffForm(prev => ({ ...prev, providerSpeciality: e.target.value }))}
/>
</div>
<div className="form-info-box">
<p>💡 The doctor will be registered as a provider under <strong>{activeHospital?.name || 'this facility'}</strong> and can log in to the provider portal.</p>
</div>
<div className="form-actions form-actions-grid-span">
<button type="button" className="action-btn cancel-btn" onClick={() => setModalType(null)}>Cancel</button>
<button type="submit" className="action-btn-primary">Register Doctor</button>
</div>
</div>
</>
) : ( ) : (
/* ── CAREGIVER FORM (local) ── */
<>
<div className="form-row-double"> <div className="form-row-double">
<div className="form-group"> <div className="form-group">
<label>Role Category</label> <label>Role Category</label>
@@ -1344,6 +1792,18 @@ export default function HospitalNetwork({ notify }) {
<option value="Occupational Therapist">Occupational Therapist</option> <option value="Occupational Therapist">Occupational Therapist</option>
</select> </select>
</div> </div>
<div className="form-group">
<label>Full Legal Name *</label>
<input
type="text" required
placeholder="e.g., Emily Davis"
value={staffForm.name}
onChange={(e) => setStaffForm(prev => ({ ...prev, name: e.target.value }))}
/>
</div>
</div>
<div className="form-row-double">
<div className="form-group"> <div className="form-group">
<label>Certifications / Licenses</label> <label>Certifications / Licenses</label>
<input <input
@@ -1353,56 +1813,35 @@ export default function HospitalNetwork({ notify }) {
onChange={(e) => setStaffForm(prev => ({ ...prev, certification: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, certification: e.target.value }))}
/> />
</div> </div>
</div>
)}
<div className="form-row-double">
<div className="form-group"> <div className="form-group">
<label>Primary Phone</label> <label>Primary Phone</label>
<input <input
type="text" type="text"
placeholder="+1 (555) 000-0000" placeholder="+91 9000000000"
value={staffForm.phone} value={staffForm.phone}
onChange={(e) => setStaffForm(prev => ({ ...prev, phone: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, phone: e.target.value }))}
/> />
</div> </div>
<div className="form-group">
<label>Work Email</label>
<input
type="email"
placeholder="e.g., r.chen@hospitals.com"
value={staffForm.email}
onChange={(e) => setStaffForm(prev => ({ ...prev, email: e.target.value }))}
/>
</div>
</div> </div>
<div className="form-group"> <div className="form-group">
<label>Operations Status</label> <label>Work Status</label>
<select <select
value={staffForm.status} value={staffForm.status}
onChange={(e) => setStaffForm(prev => ({ ...prev, status: e.target.value }))} onChange={(e) => setStaffForm(prev => ({ ...prev, status: e.target.value }))}
> >
{staffForm.type === 'doctor' ? (
<>
<option value="On Duty">On Duty</option>
<option value="Off Duty">Off Duty</option>
<option value="On Call">On Call</option>
</>
) : (
<>
<option value="Active">Active</option> <option value="Active">Active</option>
<option value="On Leave">On Leave</option> <option value="On Leave">On Leave</option>
<option value="Scheduled">Scheduled</option> <option value="Scheduled">Scheduled</option>
</>
)}
</select> </select>
</div> </div>
<div className="form-actions"> <div className="form-actions">
<button type="button" className="action-btn cancel-btn" onClick={() => setModalType(null)}>Cancel</button> <button type="button" className="action-btn cancel-btn" onClick={() => setModalType(null)}>Cancel</button>
<button type="submit" className="action-btn-primary">Register Staff</button> <button type="submit" className="action-btn-primary">Register Caregiver</button>
</div> </div>
</>
)}
</form> </form>
)} )}
</div> </div>
+8 -1
View File
@@ -145,13 +145,20 @@ export const adminService = {
saveGroup: (data) => data.id ? api.put(`/v1/resource/hospitalgroups/${data.id}`, data) : api.post('/v1/resource/hospitalgroups', data), saveGroup: (data) => data.id ? api.put(`/v1/resource/hospitalgroups/${data.id}`, data) : api.post('/v1/resource/hospitalgroups', data),
deleteGroup: (id) => api.delete(`/v1/resource/hospitalgroups/${id}`), deleteGroup: (id) => api.delete(`/v1/resource/hospitalgroups/${id}`),
getHospitals: () => api.get('/v1/resource/hospitals'), getHospitals: (params = {}) => api.get('/v1/resource/hospitals', params),
saveHospital: (data) => data.id ? api.put(`/v1/resource/hospitals/${data.id}`, data) : api.post('/v1/resource/hospitals', data), saveHospital: (data) => data.id ? api.put(`/v1/resource/hospitals/${data.id}`, data) : api.post('/v1/resource/hospitals', data),
deleteHospital: (id) => api.delete(`/v1/resource/hospitals/${id}`), deleteHospital: (id) => api.delete(`/v1/resource/hospitals/${id}`),
getStaff: () => api.get('/v1/resource/staff'), getStaff: () => api.get('/v1/resource/staff'),
saveStaff: (data) => data.id ? api.put(`/v1/resource/staff/${data.id}`, data) : api.post('/v1/resource/staff', data), saveStaff: (data) => data.id ? api.put(`/v1/resource/staff/${data.id}`, data) : api.post('/v1/resource/staff', data),
deleteStaff: (id) => api.delete(`/v1/resource/staff/${id}`), deleteStaff: (id) => api.delete(`/v1/resource/staff/${id}`),
// Add Provider (Doctor) — POST /v1/resource/providers
addProvider: (hospitalId, providerData) => api.post('/v1/resource/providers', {
...providerData,
hospital_id: hospitalId,
}),
getProviders: (hospitalId) => api.get('/v1/resource/providers', { hospital_id: hospitalId }),
}, },
} }