Files
helix-engage/src/types/entities.ts
saridsa2 72cb192447
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(appointments): preload clinic + keep saved time on edit
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>
2026-04-15 11:56:51 +05:30

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;
};