import React, { useState } from 'react';
import { User, MapPin, FileText, Phone, Building2, SquareAsterisk, Hospital } from 'lucide-react';
import PhoneLink from './PhoneLink';
import { useAuth } from '../../contexts/AuthContext';
import toast from 'react-hot-toast';
import { RotateCcw } from 'lucide-react';
/**
* Displays detailed information about a memo, including request details, user information,
* and progress through its timeline. It groups information into sections using cards.
* Allows refreshing of OTPs for creators and approvers.
* Provides a progress bar showing the completion status based on approved timeline entries.
*/
// Map field key to {label, section, icon} (location fields merged into Request Info)
const fieldMap = {
complaint: { label: 'Complaint', section: 'Request Info', icon: FileText },
department: { label: 'Department', section: 'Request Info', icon: Building2 },
tagged_role_name: { label: 'Tagged Role', section: 'Request Info', icon: User },
role_name: { label: 'User Role', section: 'User Info', icon: User },
phone_number: { label: 'Phone', section: 'User Info', icon: Phone },
user_role_name: { label: 'User Role', section: 'User Info', icon: User },
institution_id: { label: 'Name', section: 'User Info', icon: Building2 },
attendee_otp: { label: 'Attendee OTP', section: 'User Info', icon: SquareAsterisk },
completion_otp: { label: 'Completion OTP', section: 'User Info', icon: SquareAsterisk },
current_block_name: { label: 'Block', section: 'Request Info', icon: MapPin },
current_ward_name: { label: 'Ward', section: 'Request Info', icon: MapPin },
current_floor: { label: 'Floor', section: 'Request Info', icon: MapPin },
};
// Section icons
const sectionIcons = {
'Request Info': FileText,
'User Info': User,
};
const MemoInfo = ({ info, data, memoId }) => {
// no accordion, just cards
const { user, apiService, hospital } = useAuth();
const [expanded, setExpanded] = useState(false);
const [timelineData, setTimelineData] = useState(data);
const [memoInfo, setMemoInfo] = useState(info);
const handleRefreshOtp = async () => {
try {
const result = await apiService.refreshOTP(hospital.id, memoId); // Replace with actual API call
if (result.attendee_otp || result.completion_otp) {
console.log(result);
setMemoInfo(prev => ({
...prev,
attendee_otp: result.attendee_otp ? result.attendee_otp: prev.attendee_otp,
completion_otp: result.completion_otp ? result.completion_otp: prev.completion_otp,
}));
toast.success("OTP refreshed");
}
} catch (error) {
toast.error("could not refresh OTP");
console.error("Failed to refresh OTP:", error);
}
};
if (!memoInfo) {
return (
<div className="flex items-center justify-center p-8 text-gray-500">
<div className="text-center">
<FileText className="w-12 h-12 mx-auto mb-3 text-gray-300" />
<p className="text-sm">No information available</p>
</div>
</div>
);
}
// Group fields by section
const sections = {};
Object.entries(fieldMap).forEach(([key, meta]) => {
if (memoInfo[key] !== undefined && memoInfo[key] !== null && memoInfo[key] !== '') {
sections[meta.section] = sections[meta.section] || [];
sections[meta.section].push({
key,
label: meta.label,
value: memoInfo[key],
icon: meta.icon
});
}
});
const formatValue = (value, key) => {
// Special formatting for phone numbers
if (key === 'phone_number' && typeof value === 'string') {
return (
<PhoneLink phone={value} />
);
}
if ((key === 'attendee_otp' || key === 'completion_otp') && (user.is_creator || user.is_approver)) {
return (
<div className="flex items-center gap-2">
<span className="font-mono">{value}</span>
{ key==="attendee_otp" &&
<button
onClick={handleRefreshOtp}
title="Refresh OTP"
className="inline-flex items-center gap-1 px-3 py-1.5 bg-transparent text-blue-600 border border-blue-300 text-sm rounded-md transition"
>
<RotateCcw className="w-4 h-4" />
<span>Refresh</span>
</button>}
</div>
);
}
// Truncate long text on mobile with expand option
if (typeof value === 'string' && value.length > 100) {
return (
<div className="group">
{
expanded ?
<span className="block">
{value.substring(0, 80)}...
<button className="text-blue-600 text-xs ml-1 underline" onClick={() => setExpanded(false)}>more</button>
</span> :
<span className="sm:block">
{value}
<button className="text-blue-600 text-xs ml-1 underline" onClick={() => setExpanded(true)}>less</button>
</span>
}
</div>
);
}
return value;
};
return (
<>
{/* Overall status */}
<div className="mt-6 pt-4 border-t border-gray-200">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Overall Progress</span>
<div className="flex items-center gap-4">
<span className="text-sm font-medium text-green-600">
{timelineData.filter(item => item.approved).length} approved
</span>
<span className="text-sm font-medium">
{timelineData.filter(item => item.approved).length} of {timelineData.length} completed
</span>
</div>
</div>
<div className="mt-2 w-full bg-gray-200 rounded-full h-2 overflow-hidden">
<div
className="bg-green-600 transition-all duration-300 h-full"
style={{
width: `${(timelineData.filter(item => item.approved).length / timelineData.length) * 100}%`
}}
></div>
</div>
</div>
<hr className="my-6 border-gray-200" />
{/* memo detail start */}
<div className="grid gap-6 md:grid-cols-2 px-4 sm:px-0 pb-6">
{Object.entries(sections).map(([sectionTitle, fields]) => {
const SectionIcon = sectionIcons[sectionTitle] || FileText;
return (
<div key={sectionTitle} className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-4 py-3 sm:px-6 sm:py-4 border-b border-gray-200 flex items-center space-x-3 bg-gray-50">
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">
<SectionIcon className="w-4 h-4 text-blue-600" />
</div>
<h3 className="text-base sm:text-lg font-semibold text-gray-800">
{sectionTitle}
</h3>
</div>
<div className="p-4 sm:p-6">
<div className="space-y-4 sm:space-y-0 sm:grid sm:grid-cols-2 sm:gap-x-6 sm:gap-y-4">
{fields.map(({ key, label, value, icon: FieldIcon }) => (
<div key={key}>
<div className="flex items-center space-x-2 mb-1.5">
<FieldIcon className="w-4 h-4 text-gray-500" />
<dt className="text-sm font-medium text-gray-600">{label}</dt>
</div>
<dd className="text-sm text-gray-900 break-words pl-6">
{formatValue(value, key)}
</dd>
</div>
))}
</div>
</div>
</div>
);
})}
{Object.keys(sections).length === 0 && (
<div className="bg-gray-50 rounded-lg p-8 text-center sm:col-span-2">
<FileText className="w-12 h-12 mx-auto mb-3 text-gray-300" />
<p className="text-gray-500 text-sm">No details available to display</p>
</div>
)}
</div>
</>
);
};
export default MemoInfo;
Source