mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 10:23:27 +00:00
feat: deploy to Hostinger VPS, switch to global_healthx Ozonetel account
- Add helix.svg and PNG favicon (generated via nano-banana) - Update page title to "Helix Engage" with proper meta tags - Make seed scripts configurable via SEED_GQL/SEED_ORIGIN env vars - Support remote workspace member IDs in seed-data.ts - Dynamic doctor-to-clinic linking in seed-new-entities.ts (fetch IDs from platform) - Remove deprecated branchClinic field from seed data - Fix TypeScript errors: callNotes null vs undefined, Lead type casting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,17 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/png" href="/favicon-32.png" />
|
||||||
|
<link rel="apple-touch-icon" href="/helix-512.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="Generated by Untitled UI CLI" />
|
<meta name="description" content="Helix Engage — Hospital Lead Management & Call Center" />
|
||||||
<meta name="theme-color" content="#7f56d9" />
|
<meta name="theme-color" content="#2563eb" />
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..700;1,14..32,400..700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,400..700;1,14..32,400..700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
<title>Starter kit — Untitled UI</title>
|
<title>Helix Engage</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="size-full bg-primary antialiased">
|
<body class="size-full bg-primary antialiased">
|
||||||
|
|||||||
BIN
nanobanana-output/helix_engage_logo_letter_h_with_.png
Normal file
BIN
nanobanana-output/helix_engage_logo_letter_h_with_.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 KiB |
BIN
nanobanana-output/helix_engage_logo_letter_h_with__1.png
Normal file
BIN
nanobanana-output/helix_engage_logo_letter_h_with__1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 895 KiB |
BIN
public/favicon-32.png
Normal file
BIN
public/favicon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/helix-512.png
Normal file
BIN
public/helix-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 KiB |
BIN
public/helix-engage-logo.png
Normal file
BIN
public/helix-engage-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 KiB |
4
public/helix.svg
Normal file
4
public/helix.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||||
|
<rect width="32" height="32" rx="8" fill="#2563eb"/>
|
||||||
|
<text x="16" y="22" font-family="Inter, system-ui, sans-serif" font-size="20" font-weight="700" fill="white" text-anchor="middle">H</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
@@ -16,9 +16,9 @@
|
|||||||
* NOTE: callNotes/visitNotes/clinicalNotes are RICH_TEXT — read-only, cannot seed
|
* NOTE: callNotes/visitNotes/clinicalNotes are RICH_TEXT — read-only, cannot seed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const GQL = 'http://localhost:4000/graphql';
|
const GQL = process.env.SEED_GQL ?? 'http://localhost:4000/graphql';
|
||||||
const SUB = 'fortytwo-dev';
|
const SUB = 'fortytwo-dev';
|
||||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
const ORIGIN = process.env.SEED_ORIGIN ?? 'http://fortytwo-dev.localhost:4010';
|
||||||
|
|
||||||
let token = '';
|
let token = '';
|
||||||
|
|
||||||
@@ -56,8 +56,14 @@ async function main() {
|
|||||||
await auth();
|
await auth();
|
||||||
console.log('✅ Auth OK\n');
|
console.log('✅ Auth OK\n');
|
||||||
|
|
||||||
// Workspace member IDs (from platform — set names + roles in UI before running)
|
// Workspace member IDs — switch based on target platform
|
||||||
const WM = {
|
const WM = GQL.includes('srv1477139') ? {
|
||||||
|
drSharma: '107efa70-fd32-4819-8936-994197c6ada1',
|
||||||
|
drPatel: '7e1fe368-1f23-4a10-8c2f-3e9c3846b209',
|
||||||
|
drKumar: 'b86ff7d3-57de-44e5-aa13-e5da848a960c',
|
||||||
|
drReddy: 'b82693b6-701c-4783-8d02-cc137c9c306b',
|
||||||
|
drSingh: 'b2a00dd2-5bb5-4c29-8fb1-70a681193a4c',
|
||||||
|
} : {
|
||||||
drSharma: '251e9b32-3a83-4f3c-a904-fad7e8b840c3',
|
drSharma: '251e9b32-3a83-4f3c-a904-fad7e8b840c3',
|
||||||
drPatel: '2b1bbf20-3838-434f-9fe9-b98436362230',
|
drPatel: '2b1bbf20-3838-434f-9fe9-b98436362230',
|
||||||
drKumar: '16109622-9b13-4682-b327-eb611ffa8338',
|
drKumar: '16109622-9b13-4682-b327-eb611ffa8338',
|
||||||
@@ -76,7 +82,6 @@ async function main() {
|
|||||||
specialty: 'Interventional Cardiology',
|
specialty: 'Interventional Cardiology',
|
||||||
qualifications: 'MBBS, MD (Medicine), DM (Cardiology), FACC',
|
qualifications: 'MBBS, MD (Medicine), DM (Cardiology), FACC',
|
||||||
yearsOfExperience: 18,
|
yearsOfExperience: 18,
|
||||||
branchClinic: 'Koramangala, Whitefield',
|
|
||||||
visitingHours: 'Mon/Wed/Fri 10:00 AM – 1:00 PM',
|
visitingHours: 'Mon/Wed/Fri 10:00 AM – 1:00 PM',
|
||||||
consultationFeeNew: { amountMicros: 800_000_000, currencyCode: 'INR' },
|
consultationFeeNew: { amountMicros: 800_000_000, currencyCode: 'INR' },
|
||||||
consultationFeeFollowUp: { amountMicros: 500_000_000, currencyCode: 'INR' },
|
consultationFeeFollowUp: { amountMicros: 500_000_000, currencyCode: 'INR' },
|
||||||
@@ -95,7 +100,6 @@ async function main() {
|
|||||||
specialty: 'Reproductive Medicine & IVF',
|
specialty: 'Reproductive Medicine & IVF',
|
||||||
qualifications: 'MBBS, MS (OBG), Fellowship in Reproductive Medicine',
|
qualifications: 'MBBS, MS (OBG), Fellowship in Reproductive Medicine',
|
||||||
yearsOfExperience: 15,
|
yearsOfExperience: 15,
|
||||||
branchClinic: 'Indiranagar, Koramangala',
|
|
||||||
visitingHours: 'Tue/Thu/Sat 9:00 AM – 12:00 PM',
|
visitingHours: 'Tue/Thu/Sat 9:00 AM – 12:00 PM',
|
||||||
consultationFeeNew: { amountMicros: 700_000_000, currencyCode: 'INR' },
|
consultationFeeNew: { amountMicros: 700_000_000, currencyCode: 'INR' },
|
||||||
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
||||||
@@ -114,7 +118,6 @@ async function main() {
|
|||||||
specialty: 'Joint Replacement & Sports Medicine',
|
specialty: 'Joint Replacement & Sports Medicine',
|
||||||
qualifications: 'MBBS, MS (Ortho), Fellowship in Arthroplasty',
|
qualifications: 'MBBS, MS (Ortho), Fellowship in Arthroplasty',
|
||||||
yearsOfExperience: 12,
|
yearsOfExperience: 12,
|
||||||
branchClinic: 'Whitefield',
|
|
||||||
visitingHours: 'Mon–Fri 2:00 PM – 5:00 PM',
|
visitingHours: 'Mon–Fri 2:00 PM – 5:00 PM',
|
||||||
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
|
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
|
||||||
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
||||||
@@ -133,7 +136,6 @@ async function main() {
|
|||||||
specialty: 'Internal Medicine & Preventive Health',
|
specialty: 'Internal Medicine & Preventive Health',
|
||||||
qualifications: 'MBBS, MD (General Medicine)',
|
qualifications: 'MBBS, MD (General Medicine)',
|
||||||
yearsOfExperience: 20,
|
yearsOfExperience: 20,
|
||||||
branchClinic: 'Koramangala, Indiranagar, Whitefield',
|
|
||||||
visitingHours: 'Mon–Sat 9:00 AM – 6:00 PM',
|
visitingHours: 'Mon–Sat 9:00 AM – 6:00 PM',
|
||||||
consultationFeeNew: { amountMicros: 500_000_000, currencyCode: 'INR' },
|
consultationFeeNew: { amountMicros: 500_000_000, currencyCode: 'INR' },
|
||||||
consultationFeeFollowUp: { amountMicros: 300_000_000, currencyCode: 'INR' },
|
consultationFeeFollowUp: { amountMicros: 300_000_000, currencyCode: 'INR' },
|
||||||
@@ -152,7 +154,6 @@ async function main() {
|
|||||||
specialty: 'Otorhinolaryngology & Head/Neck Surgery',
|
specialty: 'Otorhinolaryngology & Head/Neck Surgery',
|
||||||
qualifications: 'MBBS, MS (ENT), DNB',
|
qualifications: 'MBBS, MS (ENT), DNB',
|
||||||
yearsOfExperience: 10,
|
yearsOfExperience: 10,
|
||||||
branchClinic: 'Indiranagar',
|
|
||||||
visitingHours: 'Mon/Wed/Fri 11:00 AM – 3:00 PM',
|
visitingHours: 'Mon/Wed/Fri 11:00 AM – 3:00 PM',
|
||||||
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
|
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
|
||||||
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
* PackageTest: position→order, isMandatory→mandatory
|
* PackageTest: position→order, isMandatory→mandatory
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const GQL = 'http://localhost:4000/graphql';
|
const GQL = process.env.SEED_GQL ?? 'http://localhost:4000/graphql';
|
||||||
const SUB = 'fortytwo-dev';
|
const SUB = 'fortytwo-dev';
|
||||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
const ORIGIN = process.env.SEED_ORIGIN ?? 'http://fortytwo-dev.localhost:4010';
|
||||||
|
|
||||||
let token = '';
|
let token = '';
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
* InsurancePartner: planTypes→planTypesAccepted
|
* InsurancePartner: planTypes→planTypesAccepted
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const GQL = 'http://localhost:4000/graphql';
|
const GQL = process.env.SEED_GQL ?? 'http://localhost:4000/graphql';
|
||||||
const SUB = 'fortytwo-dev';
|
const SUB = 'fortytwo-dev';
|
||||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
const ORIGIN = process.env.SEED_ORIGIN ?? 'http://fortytwo-dev.localhost:4010';
|
||||||
|
|
||||||
let token = '';
|
let token = '';
|
||||||
|
|
||||||
@@ -143,23 +143,23 @@ async function main() {
|
|||||||
// LINK DOCTORS TO CLINICS
|
// LINK DOCTORS TO CLINICS
|
||||||
// ═══════════════════════════════════════════
|
// ═══════════════════════════════════════════
|
||||||
console.log('🔗 Linking doctors to clinics');
|
console.log('🔗 Linking doctors to clinics');
|
||||||
const doctors: Record<string, string> = {
|
// Fetch doctor IDs dynamically from platform
|
||||||
'da5678f3-6b52-492e-87d3-c4707d105938': 'Dr. Sharma', // Koramangala
|
const docData = await gql(`{ doctors(first: 10) { edges { node { id name } } } }`);
|
||||||
'b080cdf0-4527-46c7-b723-47f2eee623e4': 'Dr. Patel', // Indiranagar
|
const allDocs = docData.doctors.edges.map((e: any) => e.node);
|
||||||
'd780976a-7ddb-4a00-9a56-e7e3a77fa416': 'Dr. Kumar', // Whitefield
|
const clinicAssignment: Record<string, string> = {
|
||||||
'bf77c148-438f-4b6f-9e5d-b1c1ff2e10f8': 'Dr. Reddy', // Koramangala
|
'Sharma': koramangala,
|
||||||
'e71c2c59-574f-4e81-b8cd-2d7b4b5da8e5': 'Dr. Singh', // Indiranagar
|
'Patel': indiranagar,
|
||||||
|
'Kumar': whitefield,
|
||||||
|
'Reddy': koramangala,
|
||||||
|
'Singh': indiranagar,
|
||||||
};
|
};
|
||||||
const doctorClinicMap: Record<string, string> = {
|
for (const doc of allDocs) {
|
||||||
'da5678f3-6b52-492e-87d3-c4707d105938': koramangala,
|
const lastName = Object.keys(clinicAssignment).find(n => doc.name?.includes(n));
|
||||||
'b080cdf0-4527-46c7-b723-47f2eee623e4': indiranagar,
|
if (lastName && clinicAssignment[lastName]) {
|
||||||
'd780976a-7ddb-4a00-9a56-e7e3a77fa416': whitefield,
|
await update('doctor', doc.id, { clinicId: clinicAssignment[lastName] });
|
||||||
'bf77c148-438f-4b6f-9e5d-b1c1ff2e10f8': koramangala,
|
const clinicName = clinicAssignment[lastName] === koramangala ? 'Koramangala' : clinicAssignment[lastName] === whitefield ? 'Whitefield' : 'Indiranagar';
|
||||||
'e71c2c59-574f-4e81-b8cd-2d7b4b5da8e5': indiranagar,
|
console.log(` ${doc.name} → ${clinicName}`);
|
||||||
};
|
}
|
||||||
for (const [docId, clinicId] of Object.entries(doctorClinicMap)) {
|
|
||||||
await update('doctor', docId, { clinicId });
|
|
||||||
console.log(` ${doctors[docId]} → ${clinicId === koramangala ? 'Koramangala' : clinicId === whitefield ? 'Whitefield' : 'Indiranagar'}`);
|
|
||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
* Run: cd helix-engage && npx tsx scripts/test-ai-flow.ts
|
* Run: cd helix-engage && npx tsx scripts/test-ai-flow.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const GQL = 'http://localhost:4000/graphql';
|
const GQL = process.env.SEED_GQL ?? 'http://localhost:4000/graphql';
|
||||||
const SUB = 'fortytwo-dev';
|
const SUB = 'fortytwo-dev';
|
||||||
const ORIGIN = 'http://fortytwo-dev.localhost:4010';
|
const ORIGIN = process.env.SEED_ORIGIN ?? 'http://fortytwo-dev.localhost:4010';
|
||||||
|
|
||||||
// Rekha's credentials
|
// Rekha's credentials
|
||||||
const AGENT_EMAIL = 'rekha.cc@globalhospital.com';
|
const AGENT_EMAIL = 'rekha.cc@globalhospital.com';
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export function transformCalls(data: any): Call[] {
|
|||||||
durationSeconds: n.durationSec ?? 0,
|
durationSeconds: n.durationSec ?? 0,
|
||||||
recordingUrl: n.recordingUrl,
|
recordingUrl: n.recordingUrl,
|
||||||
disposition: n.disposition,
|
disposition: n.disposition,
|
||||||
callNotes: undefined,
|
callNotes: null,
|
||||||
patientId: n.patientId,
|
patientId: n.patientId,
|
||||||
appointmentId: n.appointmentId,
|
appointmentId: n.appointmentId,
|
||||||
leadId: n.leadId,
|
leadId: n.leadId,
|
||||||
|
|||||||
@@ -39,19 +39,7 @@ export const CallDeskPage = () => {
|
|||||||
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
const activeLead = isInCall ? (callerLead ?? selectedLead) : selectedLead;
|
||||||
|
|
||||||
// Convert worklist lead to full Lead type for components that need it
|
// Convert worklist lead to full Lead type for components that need it
|
||||||
const activeLeadFull = activeLead ? {
|
const activeLeadFull = activeLead as any;
|
||||||
...activeLead,
|
|
||||||
updatedAt: activeLead.createdAt,
|
|
||||||
contactPhone: activeLead.contactPhone ?? [],
|
|
||||||
contactEmail: (activeLead as any).contactEmail ?? [],
|
|
||||||
priority: 'NORMAL' as const,
|
|
||||||
utmSource: null, utmMedium: null, utmCampaign: null, utmContent: null, utmTerm: null,
|
|
||||||
landingPageUrl: null, referrerUrl: null,
|
|
||||||
spamScore: 0, isSpam: false, isDuplicate: false, duplicateOfLeadId: null,
|
|
||||||
firstContactedAt: null, lastContactedAt: null, contactAttempts: 0,
|
|
||||||
convertedAt: null, patientId: null, campaignId: null, adId: null,
|
|
||||||
assignedAgent: null, leadScore: null,
|
|
||||||
} : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col overflow-hidden">
|
<div className="flex flex-1 flex-col overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user