OncoCare

 avatar
unknown
plain_text
9 months ago
49 kB
12
Indexable
import React, { useState, useEffect } from "react";
import { Patient, Appointment, ComplianceRecord } from "@/entities/all";
import { Users, Calendar, CheckCircle, AlertTriangle } from "lucide-react";
import { isFuture, isPast, startOfToday } from "date-fns";

import StatCard from "../components/dashboard/StatCard";
import UpcomingAppointments from "../components/dashboard/UpcomingAppointments";
import RecentActivity from "../components/dashboard/RecentActivity";

export default function Dashboard() {
  const [stats, setStats] = useState({
    totalPatients: 0,
    upcomingAppointments: 0,
    complianceRate: 0,
    alertsCount: 0,
  });
  const [appointments, setAppointments] = useState([]);
  const [recentActivity, setRecentActivity] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadDashboardData();
  }, []);

  const loadDashboardData = async () => {
    setLoading(true);
    
    const patients = await Patient.list();
    const allAppointments = await Appointment.list("-scheduled_date");
    const records = await ComplianceRecord.list("-created_date", 10);

    const today = startOfToday();
    const upcoming = allAppointments.filter(apt => 
      isFuture(new Date(apt.scheduled_date)) && apt.status === "Scheduled"
    );
    
    const missed = allAppointments.filter(apt =>
      isPast(new Date(apt.scheduled_date)) && apt.status === "Scheduled"
    );

    const compliantRecords = records.filter(r => r.status === "Compliant").length;
    const complianceRate = records.length > 0 
      ? Math.round((compliantRecords / records.length) * 100) 
      : 0;

    setStats({
      totalPatients: patients.length,
      upcomingAppointments: upcoming.length,
      complianceRate,
      alertsCount: missed.length,
    });

    setAppointments(upcoming.slice(0, 5));
    setRecentActivity(records);
    setLoading(false);
  };

  return (
    <div className="p-4 md:p-8">
      <div className="max-w-7xl mx-auto">
        <div className="mb-8">
          <h1 className="text-3xl font-bold text-slate-900 mb-2">Dashboard</h1>
          <p className="text-slate-600">Overview of patient compliance and appointments</p>
        </div>

        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
          <StatCard
            title="Total Patients"
            value={stats.totalPatients}
            icon={Users}
            color="blue"
          />
          <StatCard
            title="Upcoming Appointments"
            value={stats.upcomingAppointments}
            icon={Calendar}
            color="purple"
          />
          <StatCard
            title="Compliance Rate"
            value={`${stats.complianceRate}%`}
            icon={CheckCircle}
            color="green"
          />
          <StatCard
            title="Alerts"
            value={stats.alertsCount}
            subtitle={stats.alertsCount > 0 ? "Requires attention" : "All clear"}
            icon={AlertTriangle}
            color="amber"
          />
        </div>

        <div className="grid lg:grid-cols-2 gap-6">
          <UpcomingAppointments appointments={appointments} />
          <RecentActivity records={recentActivity} />
        </div>
      </div>
    </div>
  );
}
import React, { useState, useEffect } from "react";
import { Patient } from "@/entities/Patient";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Plus, Search } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";

import PatientCard from "../components/patients/PatientCard";
import PatientForm from "../components/patients/PatientForm";

export default function Patients() {
  const navigate = useNavigate();
  const [patients, setPatients] = useState([]);
  const [filteredPatients, setFilteredPatients] = useState([]);
  const [searchQuery, setSearchQuery] = useState("");
  const [showForm, setShowForm] = useState(false);
  const [editingPatient, setEditingPatient] = useState(null);
  const [isProcessing, setIsProcessing] = useState(false);

  useEffect(() => {
    loadPatients();
  }, []);

  useEffect(() => {
    if (searchQuery) {
      const filtered = patients.filter(patient =>
        patient.full_name.toLowerCase().includes(searchQuery.toLowerCase()) ||
        patient.cancer_type.toLowerCase().includes(searchQuery.toLowerCase()) ||
        patient.medical_record_number?.toLowerCase().includes(searchQuery.toLowerCase())
      );
      setFilteredPatients(filtered);
    } else {
      setFilteredPatients(patients);
    }
  }, [searchQuery, patients]);

  const loadPatients = async () => {
    const data = await Patient.list("-created_date");
    setPatients(data);
    setFilteredPatients(data);
  };

  const handleSave = async (patientData) => {
    setIsProcessing(true);
    if (editingPatient) {
      await Patient.update(editingPatient.id, patientData);
    } else {
      await Patient.create(patientData);
    }
    setShowForm(false);
    setEditingPatient(null);
    loadPatients();
    setIsProcessing(false);
  };

  const handleEdit = (patient) => {
    setEditingPatient(patient);
    setShowForm(true);
  };

  return (
    <div className="p-4 md:p-8">
      <div className="max-w-7xl mx-auto">
        <div className="mb-8">
          <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
            <div>
              <h1 className="text-3xl font-bold text-slate-900 mb-2">Patients</h1>
              <p className="text-slate-600">Manage patient records and compliance</p>
            </div>
            <Button 
              onClick={() => setShowForm(true)}
              className="bg-blue-600 hover:bg-blue-700"
            >
              <Plus className="w-4 h-4 mr-2" />
              Add Patient
            </Button>
          </div>

          <div className="relative">
            <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
            <Input
              placeholder="Search patients by name, cancer type, or record number..."
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              className="pl-10"
            />
          </div>
        </div>

        {showForm ? (
          <PatientForm
            patient={editingPatient}
            onSave={handleSave}
            onCancel={() => {
              setShowForm(false);
              setEditingPatient(null);
            }}
            isProcessing={isProcessing}
          />
        ) : (
          <>
            {filteredPatients.length === 0 ? (
              <div className="text-center py-16">
                <div className="w-20 h-20 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
                  <Search className="w-10 h-10 text-slate-400" />
                </div>
                <h3 className="text-lg font-semibold text-slate-900 mb-2">
                  {searchQuery ? "No patients found" : "No patients yet"}
                </h3>
                <p className="text-slate-600 mb-6">
                  {searchQuery ? "Try adjusting your search" : "Add your first patient to get started"}
                </p>
                {!searchQuery && (
                  <Button onClick={() => setShowForm(true)} className="bg-blue-600 hover:bg-blue-700">
                    <Plus className="w-4 h-4 mr-2" />
                    Add Patient
                  </Button>
                )}
              </div>
            ) : (
              <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
                {filteredPatients.map((patient) => (
                  <PatientCard
                    key={patient.id}
                    patient={patient}
                    onClick={() => handleEdit(patient)}
                  />
                ))}
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}
import React, { useState, useEffect } from "react";
import { Appointment } from "@/entities/Appointment";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Plus, Calendar, Clock, User, MapPin } from "lucide-react";
import { format, isFuture, isPast } from "date-fns";

import AppointmentForm from "../components/appointments/AppointmentForm";

export default function Appointments() {
  const [appointments, setAppointments] = useState([]);
  const [showForm, setShowForm] = useState(false);
  const [editingAppointment, setEditingAppointment] = useState(null);
  const [isProcessing, setIsProcessing] = useState(false);

  useEffect(() => {
    loadAppointments();
  }, []);

  const loadAppointments = async () => {
    const data = await Appointment.list("-scheduled_date");
    setAppointments(data);
  };

  const handleSave = async (appointmentData) => {
    setIsProcessing(true);
    if (editingAppointment) {
      await Appointment.update(editingAppointment.id, appointmentData);
    } else {
      await Appointment.create(appointmentData);
    }
    setShowForm(false);
    setEditingAppointment(null);
    loadAppointments();
    setIsProcessing(false);
  };

  const handleEdit = (appointment) => {
    setEditingAppointment(appointment);
    setShowForm(true);
  };

  const getStatusColor = (date, status) => {
    if (status === "Completed") return "bg-emerald-100 text-emerald-800 border-emerald-200";
    if (status === "Missed") return "bg-red-100 text-red-800 border-red-200";
    if (status === "Cancelled") return "bg-slate-100 text-slate-800 border-slate-200";
    if (isPast(new Date(date)) && status === "Scheduled") return "bg-amber-100 text-amber-800 border-amber-200";
    return "bg-blue-100 text-blue-800 border-blue-200";
  };

  return (
    <div className="p-4 md:p-8">
      <div className="max-w-7xl mx-auto">
        <div className="mb-8">
          <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-6">
            <div>
              <h1 className="text-3xl font-bold text-slate-900 mb-2">Appointments</h1>
              <p className="text-slate-600">Manage patient appointments and follow-ups</p>
            </div>
            <Button 
              onClick={() => setShowForm(true)}
              className="bg-blue-600 hover:bg-blue-700"
            >
              <Plus className="w-4 h-4 mr-2" />
              Add Appointment
            </Button>
          </div>
        </div>

        {showForm ? (
          <AppointmentForm
            appointment={editingAppointment}
            onSave={handleSave}
            onCancel={() => {
              setShowForm(false);
              setEditingAppointment(null);
            }}
            isProcessing={isProcessing}
          />
        ) : (
          <>
            {appointments.length === 0 ? (
              <div className="text-center py-16">
                <div className="w-20 h-20 mx-auto mb-4 rounded-full bg-slate-100 flex items-center justify-center">
                  <Calendar className="w-10 h-10 text-slate-400" />
                </div>
                <h3 className="text-lg font-semibold text-slate-900 mb-2">No appointments yet</h3>
                <p className="text-slate-600 mb-6">Schedule your first appointment to get started</p>
                <Button onClick={() => setShowForm(true)} className="bg-blue-600 hover:bg-blue-700">
                  <Plus className="w-4 h-4 mr-2" />
                  Add Appointment
                </Button>
              </div>
            ) : (
              <div className="grid gap-6">
                {appointments.map((appointment) => {
                  const appointmentDate = new Date(appointment.scheduled_date);
                  return (
                    <Card 
                      key={appointment.id} 
                      className="border-slate-200 hover:border-blue-300 hover:shadow-md transition-all duration-300 cursor-pointer"
                      onClick={() => handleEdit(appointment)}
                    >
                      <CardContent className="p-6">
                        <div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
                          <div className="flex-1">
                            <div className="flex items-start justify-between mb-3">
                              <div>
                                <h3 className="font-semibold text-slate-900 text-lg mb-1">
                                  {appointment.patient_name}
                                </h3>
                                <p className="text-slate-600">{appointment.appointment_type}</p>
                              </div>
                              <Badge className={`${getStatusColor(appointmentDate, appointment.status)} border text-sm`}>
                                {appointment.status}
                              </Badge>
                            </div>

                            <div className="grid md:grid-cols-2 gap-3 text-sm text-slate-600">
                              <div className="flex items-center gap-2">
                                <Calendar className="w-4 h-4 text-slate-400" />
                                <span>{format(appointmentDate, "MMMM d, yyyy")}</span>
                              </div>
                              <div className="flex items-center gap-2">
                                <Clock className="w-4 h-4 text-slate-400" />
                                <span>{format(appointmentDate, "h:mm a")}</span>
                              </div>
                              {appointment.provider && (
                                <div className="flex items-center gap-2">
                                  <User className="w-4 h-4 text-slate-400" />
                                  <span>{appointment.provider}</span>
                                </div>
                              )}
                              {appointment.location && (
                                <div className="flex items-center gap-2">
                                  <MapPin className="w-4 h-4 text-slate-400" />
                                  <span>{appointment.location}</span>
                                </div>
                              )}
                            </div>

                            {appointment.notes && (
                              <p className="mt-3 text-sm text-slate-600 border-t border-slate-100 pt-3">
                                {appointment.notes}
                              </p>
                            )}
                          </div>
                        </div>
                      </CardContent>
                    </Card>
                  );
                })}
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}
import React from 'react';
import { Card } from "@/components/ui/card";
import { motion } from "framer-motion";

export default function StatCard({ title, value, subtitle, icon: Icon, trend, color = "blue" }) {
  const colorClasses = {
    blue: "from-blue-500 to-blue-600",
    green: "from-emerald-500 to-emerald-600",
    amber: "from-amber-500 to-amber-600",
    purple: "from-purple-500 to-purple-600",
  };

  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
    >
      <Card className="relative overflow-hidden border-slate-200 shadow-sm hover:shadow-md transition-shadow duration-300">
        <div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-br ${colorClasses[color]} opacity-5 rounded-full transform translate-x-12 -translate-y-12`} />
        
        <div className="p-6">
          <div className="flex items-start justify-between mb-4">
            <div className={`p-3 rounded-xl bg-gradient-to-br ${colorClasses[color]} bg-opacity-10`}>
              <Icon className={`w-6 h-6 text-${color}-600`} />
            </div>
            {trend && (
              <span className={`text-sm font-medium ${trend.positive ? 'text-emerald-600' : 'text-red-600'}`}>
                {trend.value}
              </span>
            )}
          </div>
          
          <p className="text-sm font-medium text-slate-600 mb-1">{title}</p>
          <h3 className="text-3xl font-bold text-slate-900 mb-1">{value}</h3>
          {subtitle && <p className="text-xs text-slate-500">{subtitle}</p>}
        </div>
      </Card>
    </motion.div>
  );
}
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Calendar, Clock, User } from "lucide-react";
import { format, isToday, isTomorrow, isPast } from "date-fns";

export default function UpcomingAppointments({ appointments }) {
  const getDateLabel = (date) => {
    if (isToday(date)) return "Today";
    if (isTomorrow(date)) return "Tomorrow";
    return format(date, "MMM d");
  };

  const getStatusColor = (date, status) => {
    if (status === "Completed") return "bg-emerald-100 text-emerald-800 border-emerald-200";
    if (status === "Missed") return "bg-red-100 text-red-800 border-red-200";
    if (isPast(date) && status === "Scheduled") return "bg-amber-100 text-amber-800 border-amber-200";
    return "bg-blue-100 text-blue-800 border-blue-200";
  };

  return (
    <Card className="border-slate-200 shadow-sm">
      <CardHeader className="border-b border-slate-100 pb-4">
        <CardTitle className="text-lg font-semibold text-slate-900 flex items-center gap-2">
          <Calendar className="w-5 h-5" />
          Upcoming Appointments
        </CardTitle>
      </CardHeader>
      <CardContent className="p-0">
        {appointments.length === 0 ? (
          <div className="p-8 text-center text-slate-500">
            <Calendar className="w-12 h-12 mx-auto mb-3 opacity-30" />
            <p>No upcoming appointments</p>
          </div>
        ) : (
          <div className="divide-y divide-slate-100">
            {appointments.map((appointment) => {
              const appointmentDate = new Date(appointment.scheduled_date);
              return (
                <div key={appointment.id} className="p-4 hover:bg-slate-50 transition-colors duration-200">
                  <div className="flex items-start justify-between gap-4">
                    <div className="flex-1">
                      <div className="flex items-center gap-2 mb-2">
                        <User className="w-4 h-4 text-slate-400" />
                        <span className="font-medium text-slate-900">{appointment.patient_name}</span>
                      </div>
                      <p className="text-sm text-slate-600 mb-2">{appointment.appointment_type}</p>
                      <div className="flex items-center gap-3 text-xs text-slate-500">
                        <span className="flex items-center gap-1">
                          <Calendar className="w-3 h-3" />
                          {getDateLabel(appointmentDate)}
                        </span>
                        <span className="flex items-center gap-1">
                          <Clock className="w-3 h-3" />
                          {format(appointmentDate, "h:mm a")}
                        </span>
                      </div>
                    </div>
                    <Badge className={`${getStatusColor(appointmentDate, appointment.status)} border text-xs`}>
                      {appointment.status}
                    </Badge>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
import React from 'react';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Activity, CheckCircle, XCircle, Clock } from "lucide-react";
import { format } from "date-fns";

export default function RecentActivity({ records }) {
  const getStatusIcon = (status) => {
    switch (status) {
      case "Compliant":
        return <CheckCircle className="w-4 h-4 text-emerald-600" />;
      case "Non-Compliant":
        return <XCircle className="w-4 h-4 text-red-600" />;
      case "Partial":
        return <Clock className="w-4 h-4 text-amber-600" />;
      default:
        return <Clock className="w-4 h-4 text-slate-400" />;
    }
  };

  return (
    <Card className="border-slate-200 shadow-sm">
      <CardHeader className="border-b border-slate-100 pb-4">
        <CardTitle className="text-lg font-semibold text-slate-900 flex items-center gap-2">
          <Activity className="w-5 h-5" />
          Recent Activity
        </CardTitle>
      </CardHeader>
      <CardContent className="p-0">
        {records.length === 0 ? (
          <div className="p-8 text-center text-slate-500">
            <Activity className="w-12 h-12 mx-auto mb-3 opacity-30" />
            <p>No recent activity</p>
          </div>
        ) : (
          <div className="divide-y divide-slate-100">
            {records.map((record) => (
              <div key={record.id} className="p-4 hover:bg-slate-50 transition-colors duration-200">
                <div className="flex items-start gap-3">
                  <div className="mt-0.5">{getStatusIcon(record.status)}</div>
                  <div className="flex-1">
                    <p className="font-medium text-slate-900 text-sm mb-1">{record.patient_name}</p>
                    <p className="text-sm text-slate-600 mb-1">{record.description}</p>
                    <div className="flex items-center gap-2 text-xs text-slate-500">
                      <span>{record.record_type}</span>
                      <span>•</span>
                      <span>{format(new Date(record.due_date), "MMM d, yyyy")}</span>
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
import React from 'react';
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { User, Calendar, Activity, ChevronRight } from "lucide-react";
import { motion } from "framer-motion";

export default function PatientCard({ patient, onClick }) {
  const getStatusColor = (status) => {
    switch (status) {
      case "Active Treatment":
        return "bg-blue-100 text-blue-800 border-blue-200";
      case "Follow-up":
        return "bg-purple-100 text-purple-800 border-purple-200";
      case "Remission":
        return "bg-emerald-100 text-emerald-800 border-emerald-200";
      case "Surveillance":
        return "bg-slate-100 text-slate-800 border-slate-200";
      default:
        return "bg-slate-100 text-slate-800 border-slate-200";
    }
  };

  const getComplianceColor = (score) => {
    if (score >= 80) return "text-emerald-600";
    if (score >= 60) return "text-amber-600";
    return "text-red-600";
  };

  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
    >
      <Card 
        className="border-slate-200 hover:border-blue-300 hover:shadow-md transition-all duration-300 cursor-pointer"
        onClick={onClick}
      >
        <div className="p-6">
          <div className="flex items-start justify-between mb-4">
            <div className="flex items-start gap-3">
              <div className="w-12 h-12 rounded-full bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-semibold text-lg shadow-md">
                {patient.full_name.charAt(0)}
              </div>
              <div>
                <h3 className="font-semibold text-slate-900 text-lg mb-1">{patient.full_name}</h3>
                <p className="text-sm text-slate-600">{patient.cancer_type}</p>
              </div>
            </div>
            <ChevronRight className="w-5 h-5 text-slate-400" />
          </div>

          <div className="space-y-3">
            <div className="flex items-center justify-between">
              <Badge className={`${getStatusColor(patient.status)} border text-xs`}>
                {patient.status}
              </Badge>
              {patient.compliance_score !== undefined && (
                <div className="flex items-center gap-2">
                  <Activity className="w-4 h-4 text-slate-400" />
                  <span className={`text-sm font-semibold ${getComplianceColor(patient.compliance_score)}`}>
                    {patient.compliance_score}% Compliant
                  </span>
                </div>
              )}
            </div>

            <div className="flex items-center gap-4 text-xs text-slate-500">
              {patient.cancer_stage && (
                <span className="flex items-center gap-1">
                  <Activity className="w-3 h-3" />
                  {patient.cancer_stage}
                </span>
              )}
              {patient.primary_oncologist && (
                <span className="flex items-center gap-1">
                  <User className="w-3 h-3" />
                  Dr. {patient.primary_oncologist}
                </span>
              )}
            </div>
          </div>
        </div>
      </Card>
    </motion.div>
  );
}
import React, { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { X } from "lucide-react";

export default function PatientForm({ patient, onSave, onCancel, isProcessing }) {
  const [formData, setFormData] = useState(patient || {
    full_name: "",
    date_of_birth: "",
    medical_record_number: "",
    cancer_type: "",
    cancer_stage: "",
    diagnosis_date: "",
    treatment_plan: "",
    primary_oncologist: "",
    phone: "",
    email: "",
    emergency_contact: "",
    status: "Active Treatment",
    compliance_score: 0,
    notes: "",
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    onSave(formData);
  };

  const handleChange = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };

  return (
    <Card className="border-slate-200 shadow-lg">
      <CardHeader className="border-b border-slate-100">
        <div className="flex items-center justify-between">
          <CardTitle className="text-xl font-semibold text-slate-900">
            {patient ? "Edit Patient" : "New Patient"}
          </CardTitle>
          <Button variant="ghost" size="icon" onClick={onCancel}>
            <X className="w-5 h-5" />
          </Button>
        </div>
      </CardHeader>
      <form onSubmit={handleSubmit}>
        <CardContent className="p-6 space-y-6">
          <div className="grid md:grid-cols-2 gap-6">
            <div className="space-y-2">
              <Label htmlFor="full_name">Full Name *</Label>
              <Input
                id="full_name"
                value={formData.full_name}
                onChange={(e) => handleChange("full_name", e.target.value)}
                required
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="date_of_birth">Date of Birth</Label>
              <Input
                id="date_of_birth"
                type="date"
                value={formData.date_of_birth}
                onChange={(e) => handleChange("date_of_birth", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="medical_record_number">Medical Record #</Label>
              <Input
                id="medical_record_number"
                value={formData.medical_record_number}
                onChange={(e) => handleChange("medical_record_number", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="cancer_type">Cancer Type *</Label>
              <Input
                id="cancer_type"
                value={formData.cancer_type}
                onChange={(e) => handleChange("cancer_type", e.target.value)}
                required
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="cancer_stage">Cancer Stage</Label>
              <Select
                value={formData.cancer_stage}
                onValueChange={(value) => handleChange("cancer_stage", value)}
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select stage" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="Stage I">Stage I</SelectItem>
                  <SelectItem value="Stage II">Stage II</SelectItem>
                  <SelectItem value="Stage III">Stage III</SelectItem>
                  <SelectItem value="Stage IV">Stage IV</SelectItem>
                  <SelectItem value="In Remission">In Remission</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label htmlFor="diagnosis_date">Diagnosis Date</Label>
              <Input
                id="diagnosis_date"
                type="date"
                value={formData.diagnosis_date}
                onChange={(e) => handleChange("diagnosis_date", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="primary_oncologist">Primary Oncologist</Label>
              <Input
                id="primary_oncologist"
                value={formData.primary_oncologist}
                onChange={(e) => handleChange("primary_oncologist", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="status">Status</Label>
              <Select
                value={formData.status}
                onValueChange={(value) => handleChange("status", value)}
              >
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="Active Treatment">Active Treatment</SelectItem>
                  <SelectItem value="Follow-up">Follow-up</SelectItem>
                  <SelectItem value="Remission">Remission</SelectItem>
                  <SelectItem value="Surveillance">Surveillance</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label htmlFor="phone">Phone</Label>
              <Input
                id="phone"
                type="tel"
                value={formData.phone}
                onChange={(e) => handleChange("phone", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="email">Email</Label>
              <Input
                id="email"
                type="email"
                value={formData.email}
                onChange={(e) => handleChange("email", e.target.value)}
              />
            </div>
          </div>

          <div className="space-y-2">
            <Label htmlFor="treatment_plan">Treatment Plan</Label>
            <Textarea
              id="treatment_plan"
              value={formData.treatment_plan}
              onChange={(e) => handleChange("treatment_plan", e.target.value)}
              rows={3}
            />
          </div>

          <div className="space-y-2">
            <Label htmlFor="emergency_contact">Emergency Contact</Label>
            <Input
              id="emergency_contact"
              value={formData.emergency_contact}
              onChange={(e) => handleChange("emergency_contact", e.target.value)}
            />
          </div>

          <div className="space-y-2">
            <Label htmlFor="notes">Notes</Label>
            <Textarea
              id="notes"
              value={formData.notes}
              onChange={(e) => handleChange("notes", e.target.value)}
              rows={3}
            />
          </div>
        </CardContent>
        <CardFooter className="border-t border-slate-100 p-6 flex justify-end gap-3">
          <Button type="button" variant="outline" onClick={onCancel} disabled={isProcessing}>
            Cancel
          </Button>
          <Button type="submit" disabled={isProcessing} className="bg-blue-600 hover:bg-blue-700">
            {isProcessing ? "Saving..." : "Save Patient"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { X } from "lucide-react";
import { Patient } from "@/entities/Patient";

export default function AppointmentForm({ appointment, onSave, onCancel, isProcessing }) {
  const [patients, setPatients] = useState([]);
  const [formData, setFormData] = useState(appointment || {
    patient_id: "",
    patient_name: "",
    appointment_type: "",
    scheduled_date: "",
    status: "Scheduled",
    provider: "",
    location: "",
    notes: "",
  });

  useEffect(() => {
    loadPatients();
  }, []);

  const loadPatients = async () => {
    const data = await Patient.list();
    setPatients(data);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    onSave(formData);
  };

  const handleChange = (field, value) => {
    setFormData(prev => ({ ...prev, [field]: value }));
  };

  const handlePatientChange = (patientId) => {
    const patient = patients.find(p => p.id === patientId);
    setFormData(prev => ({
      ...prev,
      patient_id: patientId,
      patient_name: patient?.full_name || ""
    }));
  };

  return (
    <Card className="border-slate-200 shadow-lg">
      <CardHeader className="border-b border-slate-100">
        <div className="flex items-center justify-between">
          <CardTitle className="text-xl font-semibold text-slate-900">
            {appointment ? "Edit Appointment" : "New Appointment"}
          </CardTitle>
          <Button variant="ghost" size="icon" onClick={onCancel}>
            <X className="w-5 h-5" />
          </Button>
        </div>
      </CardHeader>
      <form onSubmit={handleSubmit}>
        <CardContent className="p-6 space-y-6">
          <div className="grid md:grid-cols-2 gap-6">
            <div className="space-y-2">
              <Label htmlFor="patient_id">Patient *</Label>
              <Select
                value={formData.patient_id}
                onValueChange={handlePatientChange}
                required
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select patient" />
                </SelectTrigger>
                <SelectContent>
                  {patients.map(patient => (
                    <SelectItem key={patient.id} value={patient.id}>
                      {patient.full_name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label htmlFor="appointment_type">Appointment Type *</Label>
              <Select
                value={formData.appointment_type}
                onValueChange={(value) => handleChange("appointment_type", value)}
                required
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select type" />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="Follow-up Visit">Follow-up Visit</SelectItem>
                  <SelectItem value="Imaging Scan">Imaging Scan</SelectItem>
                  <SelectItem value="Blood Work">Blood Work</SelectItem>
                  <SelectItem value="Chemotherapy">Chemotherapy</SelectItem>
                  <SelectItem value="Radiation">Radiation</SelectItem>
                  <SelectItem value="Consultation">Consultation</SelectItem>
                  <SelectItem value="Surgery">Surgery</SelectItem>
                  <SelectItem value="Other">Other</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label htmlFor="scheduled_date">Date & Time *</Label>
              <Input
                id="scheduled_date"
                type="datetime-local"
                value={formData.scheduled_date}
                onChange={(e) => handleChange("scheduled_date", e.target.value)}
                required
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="status">Status</Label>
              <Select
                value={formData.status}
                onValueChange={(value) => handleChange("status", value)}
              >
                <SelectTrigger>
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="Scheduled">Scheduled</SelectItem>
                  <SelectItem value="Completed">Completed</SelectItem>
                  <SelectItem value="Missed">Missed</SelectItem>
                  <SelectItem value="Cancelled">Cancelled</SelectItem>
                  <SelectItem value="Rescheduled">Rescheduled</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-2">
              <Label htmlFor="provider">Provider</Label>
              <Input
                id="provider"
                value={formData.provider}
                onChange={(e) => handleChange("provider", e.target.value)}
              />
            </div>
            <div className="space-y-2">
              <Label htmlFor="location">Location</Label>
              <Input
                id="location"
                value={formData.location}
                onChange={(e) => handleChange("location", e.target.value)}
              />
            </div>
          </div>

          <div className="space-y-2">
            <Label htmlFor="notes">Notes</Label>
            <Textarea
              id="notes"
              value={formData.notes}
              onChange={(e) => handleChange("notes", e.target.value)}
              rows={3}
            />
          </div>
        </CardContent>
        <CardFooter className="border-t border-slate-100 p-6 flex justify-end gap-3">
          <Button type="button" variant="outline" onClick={onCancel} disabled={isProcessing}>
            Cancel
          </Button>
          <Button type="submit" disabled={isProcessing} className="bg-blue-600 hover:bg-blue-700">
            {isProcessing ? "Saving..." : "Save Appointment"}
          </Button>
        </CardFooter>
      </form>
    </Card>
  );
}
{
  "name": "Patient",
  "type": "object",
  "properties": {
    "full_name": {
      "type": "string",
      "description": "Patient's full name"
    },
    "date_of_birth": {
      "type": "string",
      "format": "date",
      "description": "Patient's date of birth"
    },
    "medical_record_number": {
      "type": "string",
      "description": "Unique medical record identifier"
    },
    "cancer_type": {
      "type": "string",
      "description": "Type of cancer diagnosis"
    },
    "cancer_stage": {
      "type": "string",
      "enum": [
        "Stage I",
        "Stage II",
        "Stage III",
        "Stage IV",
        "In Remission"
      ],
      "description": "Cancer stage"
    },
    "diagnosis_date": {
      "type": "string",
      "format": "date",
      "description": "Date of diagnosis"
    },
    "treatment_plan": {
      "type": "string",
      "description": "Current treatment plan"
    },
    "primary_oncologist": {
      "type": "string",
      "description": "Name of primary oncologist"
    },
    "phone": {
      "type": "string",
      "description": "Patient contact phone"
    },
    "email": {
      "type": "string",
      "format": "email",
      "description": "Patient email"
    },
    "emergency_contact": {
      "type": "string",
      "description": "Emergency contact information"
    },
    "compliance_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 100,
      "description": "Overall compliance percentage"
    },
    "status": {
      "type": "string",
      "enum": [
        "Active Treatment",
        "Follow-up",
        "Remission",
        "Surveillance"
      ],
      "default": "Active Treatment",
      "description": "Current patient status"
    },
    "notes": {
      "type": "string",
      "description": "Additional notes"
    }
  },
  "required": [
    "full_name",
    "cancer_type"
  ]
}
{
  "name": "Appointment",
  "type": "object",
  "properties": {
    "patient_id": {
      "type": "string",
      "description": "Reference to patient"
    },
    "patient_name": {
      "type": "string",
      "description": "Patient name for easy reference"
    },
    "appointment_type": {
      "type": "string",
      "enum": [
        "Follow-up Visit",
        "Imaging Scan",
        "Blood Work",
        "Chemotherapy",
        "Radiation",
        "Consultation",
        "Surgery",
        "Other"
      ],
      "description": "Type of appointment"
    },
    "scheduled_date": {
      "type": "string",
      "format": "date-time",
      "description": "Scheduled appointment date and time"
    },
    "status": {
      "type": "string",
      "enum": [
        "Scheduled",
        "Completed",
        "Missed",
        "Cancelled",
        "Rescheduled"
      ],
      "default": "Scheduled",
      "description": "Appointment status"
    },
    "provider": {
      "type": "string",
      "description": "Healthcare provider name"
    },
    "location": {
      "type": "string",
      "description": "Appointment location"
    },
    "notes": {
      "type": "string",
      "description": "Appointment notes"
    },
    "reminder_sent": {
      "type": "boolean",
      "default": false,
      "description": "Whether reminder was sent"
    }
  },
  "required": [
    "patient_id",
    "patient_name",
    "appointment_type",
    "scheduled_date"
  ]
}
{
  "name": "ComplianceRecord",
  "type": "object",
  "properties": {
    "patient_id": {
      "type": "string",
      "description": "Reference to patient"
    },
    "patient_name": {
      "type": "string",
      "description": "Patient name for easy reference"
    },
    "record_type": {
      "type": "string",
      "enum": [
        "Medication",
        "Test",
        "Appointment",
        "Lifestyle",
        "Other"
      ],
      "description": "Type of compliance record"
    },
    "description": {
      "type": "string",
      "description": "What was being tracked"
    },
    "due_date": {
      "type": "string",
      "format": "date",
      "description": "When this was due"
    },
    "completion_date": {
      "type": "string",
      "format": "date",
      "description": "When this was completed"
    },
    "status": {
      "type": "string",
      "enum": [
        "Compliant",
        "Non-Compliant",
        "Partial",
        "Pending"
      ],
      "default": "Pending",
      "description": "Compliance status"
    },
    "notes": {
      "type": "string",
      "description": "Additional notes"
    }
  },
  "required": [
    "patient_id",
    "patient_name",
    "record_type",
    "description",
    "due_date"
  ]
}
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { LayoutDashboard, Users, Calendar, Activity } from "lucide-react";
import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarHeader,
  SidebarProvider,
  SidebarTrigger,
} from "@/components/ui/sidebar";

const navigationItems = [
  {
    title: "Dashboard",
    url: createPageUrl("Dashboard"),
    icon: LayoutDashboard,
  },
  {
    title: "Patients",
    url: createPageUrl("Patients"),
    icon: Users,
  },
  {
    title: "Appointments",
    url: createPageUrl("Appointments"),
    icon: Calendar,
  },
];

export default function Layout({ children, currentPageName }) {
  const location = useLocation();

  return (
    <SidebarProvider>
      <div className="min-h-screen flex w-full bg-gradient-to-br from-slate-50 to-blue-50">
        <style>{`
          :root {
            --primary: 213 94% 45%;
            --primary-foreground: 0 0% 100%;
            --accent: 152 69% 47%;
            --accent-foreground: 0 0% 100%;
            --destructive: 0 84% 60%;
          }
        `}</style>
        
        <Sidebar className="border-r border-slate-200 bg-white/80 backdrop-blur-sm">
          <SidebarHeader className="border-b border-slate-200 p-6">
            <div className="flex items-center gap-3">
              <div className="w-10 h-10 bg-gradient-to-br from-blue-600 to-blue-700 rounded-xl flex items-center justify-center shadow-lg">
                <Activity className="w-6 h-6 text-white" />
              </div>
              <div>
                <h2 className="font-semibold text-slate-900 text-lg">OncoTrack</h2>
                <p className="text-xs text-slate-500">Compliance Management</p>
              </div>
            </div>
          </SidebarHeader>
          
          <SidebarContent className="p-3">
            <SidebarGroup>
              <SidebarGroupLabel className="text-xs font-medium text-slate-500 uppercase tracking-wider px-3 py-2">
                Navigation
              </SidebarGroupLabel>
              <SidebarGroupContent>
                <SidebarMenu>
                  {navigationItems.map((item) => (
                    <SidebarMenuItem key={item.title}>
                      <SidebarMenuButton 
                        asChild 
                        className={`hover:bg-blue-50 hover:text-blue-700 transition-all duration-200 rounded-lg mb-1 ${
                          location.pathname === item.url ? 'bg-blue-50 text-blue-700 shadow-sm' : ''
                        }`}
                      >
                        <Link to={item.url} className="flex items-center gap-3 px-3 py-2.5">
                          <item.icon className="w-5 h-5" />
                          <span className="font-medium">{item.title}</span>
                        </Link>
                      </SidebarMenuButton>
                    </SidebarMenuItem>
                  ))}
                </SidebarMenu>
              </SidebarGroupContent>
            </SidebarGroup>
          </SidebarContent>
        </Sidebar>

        <main className="flex-1 flex flex-col">
          <header className="bg-white/80 backdrop-blur-sm border-b border-slate-200 px-6 py-4 md:hidden">
            <div className="flex items-center gap-4">
              <SidebarTrigger className="hover:bg-slate-100 p-2 rounded-lg transition-colors duration-200" />
              <h1 className="text-xl font-semibold text-slate-900">OncoTrack</h1>
            </div>
          </header>

          <div className="flex-1 overflow-auto">
            {children}
          </div>
        </main>
      </div>
    </SidebarProvider>
  );
}
Editor is loading...
Leave a Comment