Zuma32

Individaul Portal

Aug 7th, 2025
195
0
21 hours
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 16.55 KB | Source Code | 0 0
  1. import React, { useEffect, useState } from 'react';
  2. import { User, MapPin, Phone, Clock, AlertTriangle, Calendar, Ambulance, Bell } from 'lucide-react';
  3. import { useAuth } from './authContext';
  4. import SimpleMap from './map';
  5. import { useNavigate } from 'react-router-dom';
  6. import axios from 'axios';
  7. import WebSocketDemo from './websocket';
  8.  
  9. // EmergencyModal moved outside
  10. const EmergencyModal = ({
  11.   hospital,
  12.   setHospital,
  13.   emergencyDetail,
  14.   setEmergencyDetail,
  15.   setShowEmergencyModal,
  16.   handleEmergencyRequest,
  17. }) => {
  18.  
  19.   const [hospitals, setHospitals] = useState([])
  20.   const [selectedHospital, setSelectedHospital] = useState("");
  21.   const [location, setLocation] = useState("");
  22.   const [address, setAddress] = useState("");
  23.  
  24.  
  25.   // Fetch all hospitals from API
  26.   useEffect(() => {
  27.     const getHospitals = async () => {
  28.       try {
  29.         const response = await axios.get(
  30.           "http://localhost:8000/ambulance/hospital/"
  31.         );
  32.         const data = response.data;
  33.         setHospitals(Array.isArray(data.data) ? data.data : []);
  34.       } catch (error) {
  35.         setHospitals([]);
  36.       }
  37.     };
  38.     getHospitals();
  39.   }, []);
  40.  
  41.   // Get user location and set location state on mount
  42.   useEffect(() => {
  43.     if (navigator.geolocation) {
  44.       navigator.geolocation.getCurrentPosition(
  45.         (position) => {
  46.           const coords = `${position.coords.latitude},${position.coords.longitude}`;
  47.           setLocation(coords);
  48.         },
  49.         (error) => {
  50.           setLocation("");
  51.         }
  52.       );
  53.     } else {
  54.       setLocation("");
  55.     }
  56.   }, []);
  57.  
  58.   // When location changes and is not empty, reverse geocode automatically
  59.   useEffect(() => {
  60.     const getUserLocation = async () => {
  61.       try {
  62.         const [lat, lng] = location.split(',').map(coord => coord.trim());
  63.         if (!lat || !lng) return;
  64.         const response = await axios.get(
  65.           `https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${lat}&lon=${lng}`
  66.         );
  67.         setAddress(response.data.display_name || "");
  68.       } catch (error) {
  69.         setAddress("");
  70.       }
  71.     };
  72.     if (location) {
  73.       getUserLocation();
  74.     } else {
  75.       setAddress("");
  76.     }
  77.   }, [location]);
  78.  
  79.   // Determine if the form is valid
  80.   const isFormValid = selectedHospital && emergencyDetail.trim();
  81.  
  82.   return (
  83.     <div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50">
  84.       <div className="bg-gray-800 rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl border border-gray-700">
  85.         <div className="flex items-center gap-3 mb-6">
  86.           <AlertTriangle className="text-red-400" size={32} />
  87.           <h2 className="text-2xl font-bold text-red-400">Emergency Request</h2>
  88.         </div>
  89.         <div className="space-y-4">
  90.           <div className="flex items-center gap-2">
  91.           {address && (
  92.             <div className="text-xs text-gray-400 mt-1">Address: {address}</div>
  93.           )}
  94.             <MapPin className="w-10 h-10 text-blue-400" />
  95.           </div>
  96.          
  97.           <select
  98.             value={selectedHospital}
  99.             onChange={e => setSelectedHospital(e.target.value)}
  100.             className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400"
  101.           >
  102.             <option value="">Select a hospital</option>
  103.             {Array.isArray(hospitals) && hospitals.map((hospital) => (
  104.               <option key={hospital.id} value={hospital.id}>
  105.                 {hospital.name}
  106.               </option>
  107.             ))}
  108.           </select>
  109.           <textarea
  110.             value={emergencyDetail}
  111.             onChange={e => setEmergencyDetail(e.target.value)}
  112.             placeholder="Emergency Details"
  113.             className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-400 h-20"
  114.           />
  115.           <div className="flex gap-3">
  116.             <button
  117.               onClick={() => setShowEmergencyModal(false)}
  118.               className="flex-1 p-3 border border-gray-600 rounded-lg hover:bg-gray-700 text-gray-300"
  119.             >
  120.               Cancel
  121.             </button>
  122.             <button
  123.               className="flex-1 p-3 bg-red-500 text-white rounded-lg hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed"
  124.               onClick={() =>{ handleEmergencyRequest({
  125.                 location,
  126.                 emergencyDetail,
  127.                 selectedHospital,
  128.                 address
  129.               });  setShowEmergencyModal(false)}}
  130.               disabled={!isFormValid}
  131.             >
  132.               Request Now
  133.             </button>
  134.           </div>
  135.         </div>
  136.       </div>
  137.     </div>
  138.   );
  139. };
  140.  
  141. // CasualModal moved outside
  142. const CasualModal = ({ setShowCasualModal }) => (
  143.   <div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50">
  144.     <div className="bg-gray-800 rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl border border-gray-700">
  145.       <div className="flex items-center gap-3 mb-6">
  146.         <Calendar className="text-blue-400" size={32} />
  147.         <h2 className="text-2xl font-bold text-blue-400">Schedule Ride</h2>
  148.       </div>
  149.       <div className="space-y-4">
  150.         <input
  151.           type="text"
  152.           placeholder="Pickup Location"
  153.           className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400"
  154.         />
  155.         <input
  156.           type="text"
  157.           placeholder="Destination"
  158.           className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400"
  159.         />
  160.         <input
  161.           type="datetime-local"
  162.           className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400"
  163.         />
  164.         <select className="w-full p-3 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400">
  165.           <option>Select Service Type</option>
  166.           <option>Medical Transport</option>
  167.           <option>Routine Checkup</option>
  168.           <option>Discharge Transport</option>
  169.         </select>
  170.         <div className="flex gap-3">
  171.           <button
  172.             onClick={() => setShowCasualModal(false)}
  173.             className="flex-1 p-3 border border-gray-600 rounded-lg hover:bg-gray-700 text-gray-300"
  174.           >
  175.             Cancel
  176.           </button>
  177.           <button className="flex-1 p-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
  178.             Schedule
  179.           </button>
  180.         </div>
  181.       </div>
  182.     </div>
  183.   </div>
  184. );
  185.  
  186. function IndividualPortal() {
  187.   const [user, setUser] = useState(null);
  188.   const [showEmergencyModal, setShowEmergencyModal] = useState(false);
  189.   const [showCasualModal, setShowCasualModal] = useState(false);
  190.   const { logout } = useAuth();
  191.   const navigate = useNavigate();
  192.   const [location, setLocation] = useState(null);
  193.   const [hospital, setHospital] = useState('');
  194.   const [emergencyDetail, setEmergencyDetail] = useState('');
  195.   const [requestInfo, setRequestInfo] = useState('');
  196.   const [showRequestInfo, setShowRequestInfo] = useState(false);
  197.   const [requestInfoTimestamp, setRequestInfoTimestamp] = useState(null);
  198.   const [requestInfoTitle, setRequestInfoTitle] = useState('');
  199.  
  200.   // Show popup and set timer when requestInfo changes
  201.   useEffect(() => {
  202.     if (requestInfo) {
  203.       setShowRequestInfo(true);
  204.       setRequestInfoTimestamp(new Date());
  205.       setRequestInfoTitle('Notification'); // You can customize or set dynamically
  206.       const timer = setTimeout(() => {
  207.         setShowRequestInfo(false);
  208.        
  209.       }, 5000); // 5 seconds
  210.       return () => clearTimeout(timer);
  211.     }
  212.   }, [requestInfo]);
  213.  
  214.   useEffect(() => {
  215.     const userString = localStorage.getItem('user');
  216.     if (userString) {
  217.       try {
  218.         const u = JSON.parse(userString);
  219.         setUser(u);
  220.         console.log('User found:', u.email);
  221.       } catch (error) {
  222.         console.error('Error parsing user data:', error);
  223.         localStorage.removeItem('user');
  224.       }
  225.     }
  226.   }, []);
  227.  
  228.   const handleLogout = () => {
  229.     logout();
  230.     navigate('/login', { replace: true });
  231.   };
  232.  
  233.   const handleEmergencyRequest = async ({ location, emergencyDetail, selectedHospital, address }) => {
  234.     try {
  235.       console.log(selectedHospital)
  236.       // Parse latitude and longitude from location string
  237.       const [latitude, longitude] = location.split(',').map(coord => parseFloat(coord.trim()));
  238.       const roundedLat = latitude ? Number(latitude.toFixed(6)) : null;
  239.       const roundedLng = longitude ? Number(longitude.toFixed(6)) : null;
  240.  
  241.       // Use address as detected_address
  242.       const detected_address = address;
  243.       // Use selectedHospital as hospital id
  244.       // Use emergencyDetail as emergency_details
  245.       // Use 'Emergency' as priority for now
  246.       const payload = {
  247.         priority: 'Emergency',
  248.         hospital: selectedHospital,
  249.         emergency_details: emergencyDetail,
  250.         detected_address,
  251.         latitude: roundedLat,
  252.         longitude: roundedLng,
  253.       };
  254.  
  255.       console.log(payload)
  256.       // Get token from localStorage if available
  257.       const token = localStorage.getItem('token');
  258.       await axios.post(
  259.         'http://127.0.0.1:8000/ambulance/request-ride/',
  260.         payload,
  261.         {
  262.           headers: {
  263.             ...(token ? { Authorization: `Bearer ${token}` } : {}),
  264.             'Content-Type': 'application/json',
  265.           },
  266.         }
  267.       );
  268.       // Optionally show a success message or reset form here
  269.     } catch (error) {
  270.       // Optionally show an error message
  271.       console.log(error);
  272.     }
  273.   };
  274.  
  275.   useEffect(() => {
  276.     if (navigator.geolocation) {
  277.       navigator.geolocation.getCurrentPosition(
  278.         (position) => {
  279.           setLocation(position.coords);
  280.         },
  281.         (error) => {
  282.           console.log(error);
  283.         }
  284.       );
  285.     } else {
  286.       console.log('Geolocation is not supported by this browser.');
  287.     }
  288.   }, []);
  289.  
  290.   const getRequestNotification = (data) => {
  291.     console.log(data)
  292.     setRequestInfo(data)
  293.   }
  294.  
  295.   // Popup modal for requestInfo
  296.   // const showRequestInfo = Boolean(requestInfo); // This line is no longer needed
  297.  
  298.   return (
  299.     <div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-700">
  300.       {/* Header */}
  301.       <WebSocketDemo token={localStorage.getItem("token")} getNotifications={getRequestNotification} />
  302.       <header className="bg-gray-900 shadow-sm border-b border-gray-800">
  303.         <div className="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
  304.           <div className="flex items-center gap-3">
  305.             <Ambulance className="w-10 h-10 text-red-500" />
  306.             <h1 className="text-2xl font-bold text-white tracking-wide">AmbuCare</h1>
  307.           </div>
  308.           <div className="flex items-center gap-3">
  309.             <div className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center">
  310.               <User className="text-white" size={20} />
  311.             </div>
  312.             <div>
  313.               <p className="font-medium text-white">
  314.                 {user ? user.first_name : ''} {user ? user.last_name : ''}
  315.               </p>
  316.               <p className="text-sm text-gray-400">{user ? user.role : ''}</p>
  317.             </div>
  318.             <button
  319.               className="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600"
  320.               onClick={handleLogout}
  321.             >
  322.               Logout
  323.             </button>
  324.           </div>
  325.         </div>
  326.       </header>
  327.       <div className="max-w-6xl mx-auto px-6 py-8">
  328.         <div className="grid lg:grid-cols-3 gap-8">
  329.           {/* Map Section */}
  330.           <div className="lg:col-span-2">
  331.             <div className="bg-gray-800 rounded-2xl shadow-lg p-6 border border-gray-700">
  332.               <div className="flex items-center gap-2 mb-4">
  333.                 <MapPin className="text-blue-400" size={24} />
  334.                 <h2 className="text-xl font-semibold text-white">Your Location</h2>
  335.               </div>
  336.               <div className="h-96 bg-gray-900 rounded-xl flex items-center justify-center relative z-0 border border-gray-700">
  337.                 {/* send location to map */}
  338.                 <SimpleMap ambulance_location={[requestInfo.lat, requestInfo.lng]} />
  339.               </div>
  340.             </div>
  341.           </div>
  342.           {/* Service Panel */}
  343.           <div className="space-y-6">
  344.             {/* Emergency Button */}
  345.             <div className="bg-gray-800 rounded-2xl shadow-lg p-6 border border-gray-700">
  346.               <button
  347.                 onClick={() => setShowEmergencyModal(true)}
  348.                 className="w-full bg-red-500 hover:bg-red-600 text-white p-6 rounded-xl transition-colors font-semibold text-lg shadow-md"
  349.               >
  350.                 <AlertTriangle className="mx-auto mb-3" size={32} />
  351.                 <h3 className="text-xl font-bold">Emergency</h3>
  352.                 <p className="text-sm mt-2 opacity-90">Immediate medical assistance</p>
  353.               </button>
  354.             </div>
  355.             {/* Casual Service Button */}
  356.             <div className="bg-gray-800 rounded-2xl shadow-lg p-6 border border-gray-700">
  357.               <button
  358.                 onClick={() => setShowCasualModal(true)}
  359.                 className="w-full bg-blue-500 hover:bg-blue-600 text-white p-6 rounded-xl transition-colors font-semibold text-lg shadow-md"
  360.               >
  361.                 <Calendar className="mx-auto mb-3" size={32} />
  362.                 <h3 className="text-xl font-bold">Schedule Ride</h3>
  363.                 <p className="text-sm mt-2 opacity-90">Plan your medical transport</p>
  364.               </button>
  365.             </div>
  366.             {/* Quick Stats */}
  367.             <div className="bg-gray-800 rounded-2xl shadow-lg p-6 border border-gray-700">
  368.               <h3 className="font-semibold mb-4 text-white">Quick Stats</h3>
  369.               <div className="space-y-3">
  370.                 <div className="flex items-center gap-3">
  371.                   <Clock className="text-green-400" size={16} />
  372.                   <span className="text-sm text-gray-300">Avg. Response: 8 min</span>
  373.                 </div>
  374.                 <div className="flex items-center gap-3">
  375.                   <Phone className="text-blue-400" size={16} />
  376.                   <span className="text-sm text-gray-300">24/7 Support</span>
  377.                 </div>
  378.                 <div className="flex items-center gap-3">
  379.                   <MapPin className="text-purple-400" size={16} />
  380.                   <span className="text-sm text-gray-300">12 Nearby Units</span>
  381.                 </div>
  382.               </div>
  383.             </div>
  384.           </div>
  385.         </div>
  386.       </div>
  387.       {/* Modals */}
  388.       {showEmergencyModal && (
  389.         <EmergencyModal
  390.           hospital={hospital}
  391.           setHospital={setHospital}
  392.           emergencyDetail={emergencyDetail}
  393.           setEmergencyDetail={setEmergencyDetail}
  394.           setShowEmergencyModal={setShowEmergencyModal}
  395.           handleEmergencyRequest={handleEmergencyRequest}
  396.         />
  397.       )}
  398.       {showCasualModal && <CasualModal setShowCasualModal={setShowCasualModal} />}
  399.       {/* ================================== > */}
  400.       {/* Popup for requestInfo */}
  401.       {showRequestInfo && (
  402.         <div className="fixed inset-0 flex items-center justify-center z-50">
  403.           <div className="bg-gray-900 rounded-xl shadow-2xl p-6 max-w-sm w-full border border-gray-700 flex flex-col items-center">
  404.             <div className="mb-3">
  405.               <Bell className="w-10 h-10 text-yellow-400" />
  406.             </div>
  407.             <div className="text-lg font-semibold text-white mb-1">{requestInfoTitle}</div>
  408.             {requestInfoTimestamp && (
  409.               <div className="text-xs text-gray-400 mb-2">
  410.                 {requestInfoTimestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
  411.               </div>
  412.             )}
  413.             {/* display the state of the request */}
  414.             <div className="mb-4 text-gray-200 text-center">{requestInfo.info}</div>
  415.             <button
  416.               className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 transition-colors"
  417.               onClick={() => { setShowRequestInfo(false); }}
  418.             >
  419.               Close
  420.             </button>
  421.           </div>
  422.         </div>
  423.       )}
  424.     </div>
  425.   );
  426. }
  427.  
  428. export default IndividualPortal;
  429.  
Advertisement
Add Comment
Please, Sign In to add comment