2025-08-17 14:21:29 -03:00

563 lines
21 KiB
TypeScript

"use client"
import type React from "react"
import { useState, useCallback, useMemo } from "react"
interface PatientRecord {
id: string
fullName: string
contactNumber: string
cityLocation: string
stateRegion: string
lastVisitDate: string
nextAppointmentDate: string
createdAt: string
updatedAt: string
}
interface NotificationState {
message: string
type: "success" | "error" | "info"
isVisible: boolean
}
interface FormData {
fullName: string
contactNumber: string
cityLocation: string
stateRegion: string
lastVisitDate: string
nextAppointmentDate: string
}
const useLocalStorage = <T,>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === "undefined") return initialValue
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error)
return initialValue
}
})
const setValue = useCallback(
(value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore))
}
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error)
}
},
[key, storedValue],
)
return [storedValue, setValue] as const
}
const useNotification = () => {
const [notification, setNotification] = useState<NotificationState>({
message: "",
type: "info",
isVisible: false,
})
const showNotification = useCallback((message: string, type: NotificationState["type"] = "info") => {
setNotification({ message, type, isVisible: true })
setTimeout(() => {
setNotification((prev) => ({ ...prev, isVisible: false }))
}, 4000)
}, [])
return { notification, showNotification }
}
const generatePatientId = (): string => {
return `PT${Date.now().toString().slice(-6)}${Math.random().toString(36).substr(2, 3).toUpperCase()}`
}
const formatDateDisplay = (dateString: string): string => {
if (!dateString) return "Não informado"
try {
const date = new Date(dateString)
return date.toLocaleDateString("pt-BR", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})
} catch {
return "Data inválida"
}
}
const validateFormData = (data: FormData): string[] => {
const errors: string[] = []
if (!data.fullName.trim()) errors.push("Nome completo é obrigatório")
if (!data.contactNumber.trim()) errors.push("Número de contato é obrigatório")
if (!data.cityLocation.trim()) errors.push("Cidade é obrigatória")
if (data.fullName.trim().length < 2) errors.push("Nome deve ter pelo menos 2 caracteres")
if (data.contactNumber.trim().length < 10) errors.push("Número de contato deve ter pelo menos 10 dígitos")
return errors
}
export default function HospitalManagementSystem() {
const [patientRecords, setPatientRecords] = useLocalStorage<PatientRecord[]>("hospital_patients_v3", [])
const [isModalOpen, setIsModalOpen] = useState(false)
const [editingRecord, setEditingRecord] = useState<PatientRecord | null>(null)
const [formData, setFormData] = useState<FormData>({
fullName: "",
contactNumber: "",
cityLocation: "",
stateRegion: "",
lastVisitDate: "",
nextAppointmentDate: "",
})
const { notification, showNotification } = useNotification()
const systemMetrics = useMemo(
() => ({
totalPatients: patientRecords.length,
scheduledAppointments: patientRecords.filter((record) => record.nextAppointmentDate).length,
contactsAvailable: patientRecords.filter((record) => record.contactNumber).length,
recentVisits: patientRecords.filter((record) => {
if (!record.lastVisitDate) return false
const visitDate = new Date(record.lastVisitDate)
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
return visitDate >= thirtyDaysAgo
}).length,
}),
[patientRecords],
)
const resetFormState = useCallback(() => {
setFormData({
fullName: "",
contactNumber: "",
cityLocation: "",
stateRegion: "",
lastVisitDate: "",
nextAppointmentDate: "",
})
setEditingRecord(null)
setIsModalOpen(false)
}, [])
const handleInputChange = useCallback((field: keyof FormData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}, [])
const openCreateModal = useCallback(() => {
resetFormState()
setIsModalOpen(true)
}, [resetFormState])
const openEditModal = useCallback((record: PatientRecord) => {
setFormData({
fullName: record.fullName,
contactNumber: record.contactNumber,
cityLocation: record.cityLocation,
stateRegion: record.stateRegion,
lastVisitDate: record.lastVisitDate,
nextAppointmentDate: record.nextAppointmentDate,
})
setEditingRecord(record)
setIsModalOpen(true)
}, [])
const handleSubmitForm = useCallback(
(e: React.FormEvent) => {
e.preventDefault()
const validationErrors = validateFormData(formData)
if (validationErrors.length > 0) {
showNotification(validationErrors[0], "error")
return
}
const timestamp = new Date().toISOString()
if (editingRecord) {
const updatedRecord: PatientRecord = {
...editingRecord,
...formData,
updatedAt: timestamp,
}
setPatientRecords((prev) => prev.map((record) => (record.id === editingRecord.id ? updatedRecord : record)))
showNotification("Dados do paciente atualizados com sucesso!", "success")
} else {
const newRecord: PatientRecord = {
id: generatePatientId(),
...formData,
createdAt: timestamp,
updatedAt: timestamp,
}
setPatientRecords((prev) => [...prev, newRecord])
showNotification("Paciente cadastrado no sistema!", "success")
}
resetFormState()
},
[formData, editingRecord, setPatientRecords, showNotification, resetFormState],
)
const handleDeleteRecord = useCallback(
(recordId: string) => {
const confirmDelete = window.confirm(
"Confirma a remoção deste paciente do sistema? Esta ação não pode ser desfeita.",
)
if (confirmDelete) {
setPatientRecords((prev) => prev.filter((record) => record.id !== recordId))
showNotification("Paciente removido do sistema.", "success")
}
},
[setPatientRecords, showNotification],
)
return (
<div className="medical-system">
<header className="system-header">
<div className="header-brand">
<div className="brand-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
</div>
<div className="brand-text">
<h1>MedSystem Pro</h1>
<p>Sistema Integrado de Gestão Hospitalar</p>
</div>
</div>
<div className="header-actions">
<div className="user-info">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
Dr. Admin
</div>
</div>
</header>
<main className="main-content">
<section className="quick-stats">
<div className="stat-card patients">
<div className="stat-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
</div>
<div className="stat-content">
<h3>{systemMetrics.totalPatients}</h3>
<p>Total de Pacientes</p>
</div>
</div>
<div className="stat-card appointments">
<div className="stat-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
</div>
<div className="stat-content">
<h3>{systemMetrics.scheduledAppointments}</h3>
<p>Consultas Agendadas</p>
</div>
</div>
<div className="stat-card contacts">
<div className="stat-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
</svg>
</div>
<div className="stat-content">
<h3>{systemMetrics.contactsAvailable}</h3>
<p>Contatos Disponíveis</p>
</div>
</div>
<div className="stat-card recent">
<div className="stat-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<polyline points="12,6 12,12 16,14" />
</svg>
</div>
<div className="stat-content">
<h3>{systemMetrics.recentVisits}</h3>
<p>Visitas Recentes</p>
</div>
</div>
</section>
<div className="control-panel">
<h2 className="panel-title">Registro de Pacientes</h2>
<button className="add-patient-btn" onClick={openCreateModal}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
Novo Paciente
</button>
</div>
<section className="patients-section">
<div className="section-header">
<h2 className="section-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10,9 9,9 8,9" />
</svg>
Pacientes Cadastrados
<span className="patient-count">{patientRecords.length}</span>
</h2>
</div>
{patientRecords.length === 0 ? (
<div className="empty-state">
<svg className="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
<h3 className="empty-title">Nenhum paciente cadastrado</h3>
<p className="empty-description">Clique em "Novo Paciente" para começar</p>
</div>
) : (
<div className="table-wrapper">
<table className="patients-table">
<thead>
<tr>
<th>Paciente</th>
<th>Contato</th>
<th>Localização</th>
<th>Última Consulta</th>
<th>Próxima Consulta</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{patientRecords.map((record) => (
<tr key={record.id}>
<td>
<div className="patient-name">{record.fullName}</div>
<div className="patient-id">ID: {record.id}</div>
</td>
<td>
<div className="contact-info">
<div className="phone-number">{record.contactNumber}</div>
</div>
</td>
<td>
<div className="location-info">
{record.cityLocation}
{record.stateRegion && `, ${record.stateRegion}`}
</div>
</td>
<td>
<div className="date-info">
<div className="date-label">Última</div>
{formatDateDisplay(record.lastVisitDate)}
</div>
</td>
<td>
<div className="date-info">
<div className="date-label">Próxima</div>
{formatDateDisplay(record.nextAppointmentDate)}
</div>
</td>
<td>
<div className="action-buttons">
<button className="action-btn edit-btn" onClick={() => openEditModal(record)} title="Editar">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
</button>
<button
className="action-btn delete-btn"
onClick={() => handleDeleteRecord(record.id)}
title="Excluir"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<polyline points="3,6 5,6 21,6" />
<path d="M19,6v14a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2V6" />
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
</svg>
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</section>
</main>
<footer className="system-footer">
<div className="footer-text">🏥 MedSystem Pro - Sistema Hospitalar Integrado</div>
<div className="footer-version">Versão 2.1.0 | Desenvolvido com React & TypeScript</div>
</footer>
{isModalOpen && (
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && resetFormState()}>
<div className="modal-content">
<div className="modal-header">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10,9 9,9 8,9" />
</svg>
<h2>{editingRecord ? "Editar Paciente" : "Novo Paciente"}</h2>
</div>
<div className="modal-body">
<form onSubmit={handleSubmitForm}>
<div className="form-grid">
<div className="form-group">
<label className="form-label" htmlFor="fullName">
Nome Completo *
</label>
<input
id="fullName"
type="text"
className="form-input"
value={formData.fullName}
onChange={(e) => handleInputChange("fullName", e.target.value)}
placeholder="Nome completo do paciente"
required
/>
</div>
<div className="form-group">
<label className="form-label" htmlFor="contactNumber">
Telefone *
</label>
<input
id="contactNumber"
type="tel"
className="form-input"
value={formData.contactNumber}
onChange={(e) => handleInputChange("contactNumber", e.target.value)}
placeholder="(11) 99999-9999"
required
/>
</div>
<div className="form-group">
<label className="form-label" htmlFor="cityLocation">
Cidade *
</label>
<input
id="cityLocation"
type="text"
className="form-input"
value={formData.cityLocation}
onChange={(e) => handleInputChange("cityLocation", e.target.value)}
placeholder="Cidade"
required
/>
</div>
<div className="form-group">
<label className="form-label" htmlFor="stateRegion">
Estado
</label>
<input
id="stateRegion"
type="text"
className="form-input"
value={formData.stateRegion}
onChange={(e) => handleInputChange("stateRegion", e.target.value)}
placeholder="SP, RJ, MG..."
/>
</div>
<div className="form-group">
<label className="form-label" htmlFor="lastVisitDate">
Última Consulta
</label>
<input
id="lastVisitDate"
type="date"
className="form-input"
value={formData.lastVisitDate}
onChange={(e) => handleInputChange("lastVisitDate", e.target.value)}
/>
</div>
<div className="form-group">
<label className="form-label" htmlFor="nextAppointmentDate">
Próxima Consulta
</label>
<input
id="nextAppointmentDate"
type="date"
className="form-input"
value={formData.nextAppointmentDate}
onChange={(e) => handleInputChange("nextAppointmentDate", e.target.value)}
/>
</div>
</div>
<div className="form-actions">
<button type="submit" className="save-btn">
{editingRecord ? "Atualizar" : "Cadastrar"}
</button>
<button type="button" className="cancel-btn" onClick={resetFormState}>
Cancelar
</button>
</div>
</form>
</div>
</div>
</div>
)}
{notification.isVisible && (
<div className="toast-container">
<div className={`toast ${notification.type}`}>
<div className="toast-message">{notification.message}</div>
</div>
</div>
)}
</div>
)
}