mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
The appointment-edit form opened with clinic/time blank even for
well-formed appointments because the pipeline never carried clinicId
end-to-end. Four-layer audit + fix:
1. APPOINTMENTS_QUERY now fetches clinicId + clinic { id clinicName }
+ doctor.fullName (was only doctor.id).
2. transformAppointments populates real clinicId + clinicName from the
relation instead of faking clinicName=department.
3. Appointment type gets clinicId: string | null.
4. context-panel passes clinicId through to AppointmentForm's
existingAppointment prop; form initial-states clinic from it.
Also on edit: if the saved timeSlot isn't in the fresh slot list
(past-slot filter, schedule change, clinic mismatch) we inject it as
"HH:MM (current)" so the dropdown displays the existing value instead
of looking cleared.
Historical appointments with clinicId=null on the platform still fall
through to the auto-select-from-slot logic; a maint backfill for those
is a separate task.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
355 lines
9.8 KiB
TypeScript
355 lines
9.8 KiB
TypeScript
// Lead domain
|
|
export type LeadSource =
|
|
| 'WEBSITE'
|
|
| 'FACEBOOK_AD'
|
|
| 'GOOGLE_AD'
|
|
| 'INSTAGRAM'
|
|
| 'GOOGLE_MY_BUSINESS'
|
|
| 'REFERRAL'
|
|
| 'WALK_IN'
|
|
| 'WHATSAPP'
|
|
| 'PHONE'
|
|
| 'OTHER';
|
|
|
|
export type LeadStatus =
|
|
| 'NEW'
|
|
| 'CONTACTED'
|
|
| 'QUALIFIED'
|
|
| 'NURTURING'
|
|
| 'APPOINTMENT_SET'
|
|
| 'CONVERTED'
|
|
| 'LOST';
|
|
|
|
export type Priority = 'LOW' | 'NORMAL' | 'HIGH' | 'URGENT';
|
|
|
|
// Campaign domain
|
|
export type CampaignType =
|
|
| 'OUTBOUND_CALL'
|
|
| 'WHATSAPP'
|
|
| 'SMS'
|
|
| 'EMAIL'
|
|
| 'FACEBOOK_AD'
|
|
| 'GOOGLE_AD'
|
|
| 'INSTAGRAM_AD';
|
|
|
|
export type CampaignStatus = 'DRAFT' | 'ACTIVE' | 'PAUSED' | 'COMPLETED';
|
|
|
|
export type CampaignPlatform = 'FACEBOOK' | 'GOOGLE' | 'INSTAGRAM' | 'YOUTUBE' | 'MANUAL';
|
|
|
|
// Ad domain
|
|
export type AdStatus = 'DRAFT' | 'ACTIVE' | 'PAUSED' | 'ENDED';
|
|
|
|
export type AdFormat = 'IMAGE' | 'VIDEO' | 'CAROUSEL' | 'TEXT' | 'LEAD_FORM';
|
|
|
|
// Activity domain
|
|
export type LeadActivityType =
|
|
| 'STATUS_CHANGE'
|
|
| 'CALL_MADE'
|
|
| 'CALL_RECEIVED'
|
|
| 'WHATSAPP_SENT'
|
|
| 'WHATSAPP_RECEIVED'
|
|
| 'SMS_SENT'
|
|
| 'EMAIL_SENT'
|
|
| 'EMAIL_RECEIVED'
|
|
| 'NOTE_ADDED'
|
|
| 'ASSIGNED'
|
|
| 'APPOINTMENT_BOOKED'
|
|
| 'FOLLOW_UP_CREATED'
|
|
| 'CONVERTED'
|
|
| 'MARKED_SPAM'
|
|
| 'DUPLICATE_DETECTED';
|
|
|
|
export type Channel = 'PHONE' | 'WHATSAPP' | 'SMS' | 'EMAIL' | 'IN_PERSON' | 'SYSTEM';
|
|
|
|
export type ActivityOutcome =
|
|
| 'SUCCESSFUL'
|
|
| 'NO_ANSWER'
|
|
| 'BUSY'
|
|
| 'NOT_INTERESTED'
|
|
| 'CALLBACK_REQUESTED'
|
|
| 'INFORMATION_SENT';
|
|
|
|
// Follow-up domain
|
|
export type FollowUpType = 'CALLBACK' | 'APPOINTMENT_REMINDER' | 'POST_VISIT' | 'MARKETING' | 'REVIEW_REQUEST';
|
|
|
|
export type FollowUpStatus = 'PENDING' | 'COMPLETED' | 'CANCELLED' | 'OVERDUE';
|
|
|
|
// Messaging domain
|
|
export type MessageDirection = 'CLINIC_TO_PATIENT' | 'PATIENT_TO_CLINIC';
|
|
|
|
export type MessageChannel = 'WHATSAPP' | 'SMS' | 'EMAIL' | 'IN_APP';
|
|
|
|
export type MessageStatus = 'SENT' | 'DELIVERED' | 'READ' | 'FAILED';
|
|
|
|
export type TemplateApprovalStatus = 'APPROVED' | 'PENDING' | 'REJECTED';
|
|
|
|
// System
|
|
export type HealthStatus = 'HEALTHY' | 'WARNING' | 'UNHEALTHY';
|
|
|
|
// Shared sub-types
|
|
type CurrencyAmount = {
|
|
amountMicros: number;
|
|
currencyCode: string;
|
|
};
|
|
|
|
// Entity types
|
|
export type Lead = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
leadSource: LeadSource | null;
|
|
leadStatus: LeadStatus | null;
|
|
priority: Priority | null;
|
|
contactName: { firstName: string; lastName: string } | null;
|
|
contactPhone: { number: string; callingCode: string }[] | null;
|
|
contactEmail: { address: string }[] | null;
|
|
interestedService: string | null;
|
|
assignedAgent: string | null;
|
|
utmSource: string | null;
|
|
utmMedium: string | null;
|
|
utmCampaign: string | null;
|
|
utmContent: string | null;
|
|
utmTerm: string | null;
|
|
landingPageUrl: string | null;
|
|
referrerUrl: string | null;
|
|
leadScore: number | null;
|
|
spamScore: number | null;
|
|
isSpam: boolean | null;
|
|
isDuplicate: boolean | null;
|
|
duplicateOfLeadId: string | null;
|
|
firstContactedAt: string | null;
|
|
lastContactedAt: string | null;
|
|
contactAttempts: number | null;
|
|
convertedAt: string | null;
|
|
aiSummary: string | null;
|
|
aiSuggestedAction: string | null;
|
|
patientId: string | null;
|
|
campaignId: string | null;
|
|
adId: string | null;
|
|
};
|
|
|
|
export type Campaign = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
campaignName: string | null;
|
|
campaignType: CampaignType | null;
|
|
campaignStatus: CampaignStatus | null;
|
|
platform: CampaignPlatform | null;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
budget: CurrencyAmount | null;
|
|
amountSpent: CurrencyAmount | null;
|
|
impressionCount: number | null;
|
|
clickCount: number | null;
|
|
targetCount: number | null;
|
|
contactedCount: number | null;
|
|
convertedCount: number | null;
|
|
leadCount: number | null;
|
|
externalCampaignId: string | null;
|
|
platformUrl: string | null;
|
|
};
|
|
|
|
export type Ad = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
updatedAt: string | null;
|
|
adName: string | null;
|
|
externalAdId: string | null;
|
|
adStatus: AdStatus | null;
|
|
adFormat: AdFormat | null;
|
|
headline: string | null;
|
|
adDescription: string | null;
|
|
destinationUrl: string | null;
|
|
previewUrl: string | null;
|
|
impressions: number | null;
|
|
clicks: number | null;
|
|
conversions: number | null;
|
|
spend: CurrencyAmount | null;
|
|
campaignId: string | null;
|
|
};
|
|
|
|
export type LeadActivity = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
activityType: LeadActivityType | null;
|
|
summary: string | null;
|
|
occurredAt: string | null;
|
|
performedBy: string | null;
|
|
previousValue: string | null;
|
|
newValue: string | null;
|
|
channel: Channel | null;
|
|
durationSeconds: number | null;
|
|
outcome: ActivityOutcome | null;
|
|
leadId: string | null;
|
|
};
|
|
|
|
export type FollowUp = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
followUpType: FollowUpType | null;
|
|
followUpStatus: FollowUpStatus | null;
|
|
scheduledAt: string | null;
|
|
completedAt: string | null;
|
|
priority: Priority | null;
|
|
assignedAgent: string | null;
|
|
patientId: string | null;
|
|
callId: string | null;
|
|
// Denormalized fields
|
|
patientName?: string;
|
|
patientPhone?: string;
|
|
description?: string;
|
|
};
|
|
|
|
export type PatientMessage = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
subject: string | null;
|
|
body: string | null;
|
|
direction: MessageDirection | null;
|
|
channel: MessageChannel | null;
|
|
priority: Priority | null;
|
|
sentAt: string | null;
|
|
readAt: string | null;
|
|
senderName: string | null;
|
|
patientId: string | null;
|
|
// Optional fields
|
|
status?: MessageStatus;
|
|
recipientName?: string;
|
|
recipientPhone?: string;
|
|
};
|
|
|
|
export type WhatsAppTemplate = {
|
|
id: string;
|
|
name: string | null;
|
|
body: string | null;
|
|
variables: string[];
|
|
linkedCampaignId: string | null;
|
|
linkedCampaignName: string | null;
|
|
approvalStatus: TemplateApprovalStatus | null;
|
|
language: string[];
|
|
sendCount: number | null;
|
|
deliveredCount: number | null;
|
|
readCount: number | null;
|
|
clickedCount: number | null;
|
|
failedCount: number | null;
|
|
};
|
|
|
|
export type Agent = {
|
|
id: string;
|
|
name: string | null;
|
|
initials: string | null;
|
|
isOnShift: boolean | null;
|
|
shiftNote: string | null;
|
|
avgResponseHours: number | null;
|
|
activeLeadCount: number | null;
|
|
};
|
|
|
|
// Call domain
|
|
export type CallDirection = 'INBOUND' | 'OUTBOUND';
|
|
export type CallStatus = 'RINGING' | 'IN_PROGRESS' | 'COMPLETED' | 'MISSED' | 'VOICEMAIL';
|
|
export type CallDisposition =
|
|
| 'APPOINTMENT_BOOKED'
|
|
| 'APPOINTMENT_RESCHEDULED'
|
|
| 'APPOINTMENT_CANCELLED'
|
|
| 'FOLLOW_UP_SCHEDULED'
|
|
| 'INFO_PROVIDED'
|
|
| 'WRONG_NUMBER'
|
|
| 'NO_ANSWER'
|
|
| 'NOT_INTERESTED'
|
|
| 'CALLBACK_REQUESTED'
|
|
| 'CALL_DROPPED';
|
|
|
|
export type Call = {
|
|
id: string;
|
|
createdAt: string;
|
|
callDirection: CallDirection | null;
|
|
callStatus: CallStatus | null;
|
|
callerNumber: { number: string; callingCode: string }[] | null;
|
|
agentName: string | null;
|
|
startedAt: string | null;
|
|
endedAt: string | null;
|
|
durationSeconds: number | null;
|
|
recordingUrl: string | null;
|
|
disposition: CallDisposition | null;
|
|
callNotes: string | null;
|
|
patientId: string | null;
|
|
appointmentId: string | null;
|
|
leadId: string | null;
|
|
sla?: number | null;
|
|
// Authoritative agent link from CDR enrichment. agentName remains the
|
|
// raw Ozonetel string (may be a transfer chain) for display fallback.
|
|
agentId?: string | null;
|
|
agent?: { id: string; name: string | null; ozonetelAgentId: string | null } | null;
|
|
transferredTo?: string | null;
|
|
transferType?: string | null;
|
|
// Denormalized for display
|
|
leadName?: string;
|
|
leadPhone?: string;
|
|
leadService?: string;
|
|
};
|
|
|
|
// Patient domain
|
|
export type PatientStatus = 'ACTIVE' | 'INACTIVE';
|
|
export type PatientGender = 'MALE' | 'FEMALE' | 'OTHER';
|
|
export type PatientType = 'OPD' | 'IPD' | 'EMERGENCY' | 'REGULAR';
|
|
|
|
export type Patient = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
fullName: { firstName: string; lastName: string } | null;
|
|
phones: { primaryPhoneNumber: string } | null;
|
|
emails: { primaryEmail: string } | null;
|
|
dateOfBirth: string | null;
|
|
gender: PatientGender | null;
|
|
patientType: PatientType | null;
|
|
};
|
|
|
|
// Appointment domain
|
|
export type AppointmentStatus = 'SCHEDULED' | 'CONFIRMED' | 'CHECKED_IN' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED' | 'NO_SHOW';
|
|
export type AppointmentType = 'CONSULTATION' | 'FOLLOW_UP' | 'PROCEDURE' | 'EMERGENCY';
|
|
|
|
export type Appointment = {
|
|
id: string;
|
|
createdAt: string | null;
|
|
scheduledAt: string | null;
|
|
durationMinutes: number | null;
|
|
appointmentType: AppointmentType | null;
|
|
appointmentStatus: AppointmentStatus | null;
|
|
doctorName: string | null;
|
|
doctorId: string | null;
|
|
department: string | null;
|
|
reasonForVisit: string | null;
|
|
patientId: string | null;
|
|
patientName: string | null;
|
|
patientPhone: string | null;
|
|
clinicId: string | null;
|
|
clinicName: string | null;
|
|
};
|
|
|
|
// Lead Ingestion Source domain
|
|
export type IntegrationStatus = 'ACTIVE' | 'WARNING' | 'ERROR' | 'DISABLED';
|
|
export type AuthStatus = 'VALID' | 'EXPIRING_SOON' | 'EXPIRED' | 'NOT_CONFIGURED';
|
|
export type IngestionPlatform =
|
|
| 'FACEBOOK'
|
|
| 'INSTAGRAM'
|
|
| 'GOOGLE_ADS'
|
|
| 'GOOGLE_MY_BUSINESS'
|
|
| 'WEBSITE'
|
|
| 'WHATSAPP';
|
|
|
|
export type LeadIngestionSource = {
|
|
id: string;
|
|
createdAt: string;
|
|
sourceName: string | null;
|
|
platform: IngestionPlatform | null;
|
|
integrationStatus: IntegrationStatus | null;
|
|
lastSuccessfulSyncAt: string | null;
|
|
lastFailureAt: string | null;
|
|
leadsReceivedLast24h: number | null;
|
|
consecutiveFailures: number | null;
|
|
authStatus: AuthStatus | null;
|
|
webhookUrl: string | null;
|
|
lastErrorMessage: string | null;
|
|
};
|