add HospitalNetwork management page and service integration
This commit is contained in:
+847
-281
File diff suppressed because it is too large
Load Diff
+538
-99
@@ -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>
|
||||||
|
|||||||
@@ -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 }),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user