216 lines
7.4 KiB
JavaScript
216 lines
7.4 KiB
JavaScript
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { fetchTmUsers } from '../api';
|
|
import DashboardLoading from './DashboardLoading';
|
|
import exportCsv from './exportCsv';
|
|
|
|
const SURVEY_ORIGIN = 'https://tgics.cttr.in';
|
|
const EMPTY_TOTALS = { tms: 0, contacts_created: 0, doctors_with_surveys: 0, total_surveys: 0 };
|
|
|
|
function mrWhatsappLink(row) {
|
|
const userId = String(row.user_id || '').trim();
|
|
if (!userId) {
|
|
return { href: '', label: 'Link unavailable' };
|
|
}
|
|
let digits = String(row.phone || '').replace(/\D+/g, '');
|
|
if (digits.length === 10) {
|
|
digits = `91${digits}`;
|
|
}
|
|
if (!digits) {
|
|
return { href: '', label: 'No WhatsApp number' };
|
|
}
|
|
const name = String(row.name || '').trim();
|
|
const link = `${SURVEY_ORIGIN}/${userId}`;
|
|
const text = `Dear${name ? ` ${name}` : ''},\n\nPlease share this TGICS link with your enrolled doctors. They should open it and enter their WhatsApp mobile number to continue:\n${link}`;
|
|
return {
|
|
href: `https://api.whatsapp.com/send?phone=${digits}&text=${encodeURIComponent(text)}`,
|
|
label: '',
|
|
};
|
|
}
|
|
|
|
export default function TmUsersSection({ onUnauthorized }) {
|
|
const [items, setItems] = useState([]);
|
|
const [totals, setTotals] = useState(EMPTY_TOTALS);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [query, setQuery] = useState('');
|
|
|
|
const load = useCallback(async (refresh = false) => {
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
const res = await fetchTmUsers(refresh);
|
|
setItems(Array.isArray(res.data?.items) ? res.data.items : []);
|
|
setTotals({ ...EMPTY_TOTALS, ...(res.data?.totals || {}) });
|
|
} catch (err) {
|
|
if (err.status === 401) {
|
|
onUnauthorized?.();
|
|
return;
|
|
}
|
|
setError(err.message || 'Unable to load Territory Managers.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [onUnauthorized]);
|
|
|
|
useEffect(() => {
|
|
load(false);
|
|
}, [load]);
|
|
|
|
const filtered = useMemo(() => {
|
|
const q = query.trim().toLowerCase();
|
|
if (!q) return items;
|
|
return items.filter((row) =>
|
|
[row.name, row.employee_code, row.position_name, row.position_code]
|
|
.some((v) => String(v || '').toLowerCase().includes(q))
|
|
);
|
|
}, [items, query]);
|
|
|
|
const handleExport = () => {
|
|
exportCsv(
|
|
'tgics_mr_dashboard',
|
|
['MR ID', 'Name', 'Employee code', 'Position name', 'Doctors Enrolled', 'Active Doctors', 'Total surveys'],
|
|
filtered.map((row) => [
|
|
row.user_id,
|
|
row.name,
|
|
row.employee_code,
|
|
row.position_name,
|
|
row.contacts_created,
|
|
row.doctors_with_surveys,
|
|
row.total_surveys,
|
|
])
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="dash-stats">
|
|
<div className="dash-stat primary">
|
|
<small>Total MR Count</small>
|
|
<strong>{loading ? '…' : totals.tms}</strong>
|
|
</div>
|
|
<div className="dash-stat info emphasized">
|
|
<small>Total Doctors Enrolled</small>
|
|
<strong>{loading ? '…' : totals.contacts_created}</strong>
|
|
</div>
|
|
<div className="dash-stat warning emphasized">
|
|
<small>Active Doctors</small>
|
|
<strong>{loading ? '…' : totals.doctors_with_surveys}</strong>
|
|
</div>
|
|
<div className="dash-stat success">
|
|
<small>Total Surveys</small>
|
|
<strong>{loading ? '…' : totals.total_surveys}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<main className="dash-card">
|
|
<div className="dash-toolbar">
|
|
<p className="dash-muted">
|
|
{loading
|
|
? ''
|
|
: query.trim()
|
|
? `${filtered.length} of ${items.length} MRs`
|
|
: `${items.length} MRs`}
|
|
</p>
|
|
<div className="dash-toolbar-actions">
|
|
<input
|
|
type="search"
|
|
className="dash-input"
|
|
placeholder="Search name, employee code or position"
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
/>
|
|
<button type="button" className="dash-btn info" onClick={() => load(true)} disabled={loading}>
|
|
Refresh Data
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="dash-btn dark"
|
|
onClick={handleExport}
|
|
disabled={loading || filtered.length === 0}
|
|
>
|
|
Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{error ? <p className="error">{error}</p> : null}
|
|
|
|
{loading ? <DashboardLoading /> : null}
|
|
|
|
{!loading && !error && filtered.length === 0 ? (
|
|
<p className="dash-muted dash-center">No MRs found.</p>
|
|
) : null}
|
|
|
|
{filtered.length > 0 ? (
|
|
<div className="table-wrap">
|
|
<table className="dash-table">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th className="text-left">Name</th>
|
|
<th>Employee code</th>
|
|
<th>Position name</th>
|
|
<th className="dash-col-emphasis">Doctors Enrolled</th>
|
|
<th className="dash-col-emphasis">Active Doctors</th>
|
|
<th>Total surveys</th>
|
|
<th>Doctors</th>
|
|
<th>WhatsApp</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filtered.map((row, index) => {
|
|
const whatsapp = mrWhatsappLink(row);
|
|
return (
|
|
<tr key={row.user_id}>
|
|
<td>{index + 1}</td>
|
|
<td className="text-left">{row.name || '-'}</td>
|
|
<td>{row.employee_code || '-'}</td>
|
|
<td>{row.position_name || '-'}</td>
|
|
<td className="dash-num dash-col-emphasis">{row.contacts_created}</td>
|
|
<td className="dash-num dash-col-emphasis">{row.doctors_with_surveys}</td>
|
|
<td className="dash-num">{row.total_surveys}</td>
|
|
<td>
|
|
{row.contacts_created > 0 ? (
|
|
<Link
|
|
className="dash-btn info dash-btn-link"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
to={`/dashboard/doctors?${new URLSearchParams({
|
|
mr: row.user_id,
|
|
mr_name: row.name,
|
|
mr_code: row.employee_code,
|
|
})}`}
|
|
>
|
|
View Doctors
|
|
</Link>
|
|
) : (
|
|
<span className="dash-muted">-</span>
|
|
)}
|
|
</td>
|
|
<td>
|
|
{whatsapp.href ? (
|
|
<a
|
|
className="dash-btn whatsapp dash-btn-link"
|
|
href={whatsapp.href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Send WhatsApp
|
|
</a>
|
|
) : (
|
|
<span className="dash-muted">{whatsapp.label}</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
) : null}
|
|
</main>
|
|
</>
|
|
);
|
|
}
|