Explorar el Código

Add Notify Customer button to work order header (#123)

* notification

* add notification button
Bernt Christian Egeland hace 6 meses
padre
commit
3075ced1af

+ 12 - 1
messages/de/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "E-Mail",
     "share": "Teilen",
+    "notify": "Benachrichtigen",
     "delete": "Löschen",
     "unpaid": "Unbezahlt",
     "unsavedChanges": "Nicht gespeicherte Änderungen",
@@ -163,7 +164,8 @@
     "balanceDue": "Ausstehend",
     "paidBadge": "BEZAHLT",
     "entityLabel": "Rechnung",
-    "emailSubject": "Zahlungsbestätigung"
+    "emailSubject": "Zahlungsbestätigung",
+    "statusEmailSubject": "Aktualisierung des Servicestatus"
   },
   "payments": {
     "title": "Zahlungen",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Mehr erfahren",
     "setDefaults": "Standardwerte"
+  },
+  "notifications": {
+    "title": "Kundenbenachrichtigungen",
+    "viaSms": "per SMS",
+    "viaEmail": "per E-Mail",
+    "justNow": "Gerade eben",
+    "minutesAgo": "vor {count}m",
+    "hoursAgo": "vor {count}h",
+    "daysAgo": "vor {count}d"
   }
 }

+ 12 - 1
messages/en/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Email",
     "share": "Share",
+    "notify": "Notify",
     "delete": "Delete",
     "unpaid": "Unpaid",
     "unsavedChanges": "Unsaved changes",
@@ -163,7 +164,8 @@
     "balanceDue": "Balance Due",
     "paidBadge": "PAID",
     "entityLabel": "Invoice",
-    "emailSubject": "Payment Confirmation"
+    "emailSubject": "Payment Confirmation",
+    "statusEmailSubject": "Service Status Update"
   },
   "payments": {
     "title": "Payments",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Read More",
     "setDefaults": "Set Defaults"
+  },
+  "notifications": {
+    "title": "Customer Notifications",
+    "viaSms": "via SMS",
+    "viaEmail": "via Email",
+    "justNow": "Just now",
+    "minutesAgo": "{count}m ago",
+    "hoursAgo": "{count}h ago",
+    "daysAgo": "{count}d ago"
   }
 }

+ 12 - 1
messages/es/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Correo Electrónico",
     "share": "Compartir",
+    "notify": "Notificar",
     "delete": "Eliminar",
     "unpaid": "Sin Pagar",
     "unsavedChanges": "Cambios sin guardar",
@@ -163,7 +164,8 @@
     "balanceDue": "Saldo Pendiente",
     "paidBadge": "PAGADO",
     "entityLabel": "Factura",
-    "emailSubject": "Confirmación de Pago"
+    "emailSubject": "Confirmación de Pago",
+    "statusEmailSubject": "Actualización del estado del servicio"
   },
   "payments": {
     "title": "Pagos",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Leer más",
     "setDefaults": "Configurar valores"
+  },
+  "notifications": {
+    "title": "Notificaciones al cliente",
+    "viaSms": "por SMS",
+    "viaEmail": "por correo",
+    "justNow": "Ahora mismo",
+    "minutesAgo": "hace {count}m",
+    "hoursAgo": "hace {count}h",
+    "daysAgo": "hace {count}d"
   }
 }

+ 12 - 1
messages/fr/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Email",
     "share": "Partager",
+    "notify": "Notifier",
     "delete": "Supprimer",
     "unpaid": "Non Payé",
     "unsavedChanges": "Modifications non enregistrées",
@@ -163,7 +164,8 @@
     "balanceDue": "Solde Dû",
     "paidBadge": "PAYÉ",
     "entityLabel": "Facture",
-    "emailSubject": "Confirmation de Paiement"
+    "emailSubject": "Confirmation de Paiement",
+    "statusEmailSubject": "Mise à jour du statut du service"
   },
   "payments": {
     "title": "Paiements",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "En savoir plus",
     "setDefaults": "Paramètres par défaut"
+  },
+  "notifications": {
+    "title": "Notifications client",
+    "viaSms": "par SMS",
+    "viaEmail": "par e-mail",
+    "justNow": "À l'instant",
+    "minutesAgo": "il y a {count}m",
+    "hoursAgo": "il y a {count}h",
+    "daysAgo": "il y a {count}j"
   }
 }

+ 12 - 1
messages/it/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Email",
     "share": "Condividi",
+    "notify": "Notifica",
     "delete": "Elimina",
     "unpaid": "Non Pagato",
     "unsavedChanges": "Modifiche non salvate",
@@ -163,7 +164,8 @@
     "balanceDue": "Importo Dovuto",
     "paidBadge": "PAGATO",
     "entityLabel": "Fattura",
-    "emailSubject": "Conferma Pagamento"
+    "emailSubject": "Conferma Pagamento",
+    "statusEmailSubject": "Aggiornamento dello stato del servizio"
   },
   "payments": {
     "title": "Pagamenti",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Scopri di più",
     "setDefaults": "Valori predefiniti"
+  },
+  "notifications": {
+    "title": "Notifiche al cliente",
+    "viaSms": "via SMS",
+    "viaEmail": "via e-mail",
+    "justNow": "Proprio ora",
+    "minutesAgo": "{count}m fa",
+    "hoursAgo": "{count}h fa",
+    "daysAgo": "{count}g fa"
   }
 }

+ 12 - 1
messages/nb/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "E-post",
     "share": "Del",
+    "notify": "Varsle",
     "delete": "Slett",
     "unpaid": "Ubetalt",
     "unsavedChanges": "Ulagrede endringer",
@@ -163,7 +164,8 @@
     "balanceDue": "Skyld beløp",
     "paidBadge": "BETALT",
     "entityLabel": "Faktura",
-    "emailSubject": "Betalingsbekreftelse"
+    "emailSubject": "Betalingsbekreftelse",
+    "statusEmailSubject": "Oppdatering av servicestatus"
   },
   "payments": {
     "title": "Betalinger",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Les mer",
     "setDefaults": "Standardinnstillinger"
+  },
+  "notifications": {
+    "title": "Kundevarsler",
+    "viaSms": "via SMS",
+    "viaEmail": "via e-post",
+    "justNow": "Akkurat nå",
+    "minutesAgo": "{count}m siden",
+    "hoursAgo": "{count}t siden",
+    "daysAgo": "{count}d siden"
   }
 }

+ 12 - 1
messages/nl/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "E-mail",
     "share": "Delen",
+    "notify": "Melden",
     "delete": "Verwijderen",
     "unpaid": "Onbetaald",
     "unsavedChanges": "Niet-opgeslagen wijzigingen",
@@ -163,7 +164,8 @@
     "balanceDue": "Verschuldigd bedrag",
     "paidBadge": "BETAALD",
     "entityLabel": "Factuur",
-    "emailSubject": "Betalingsbevestiging"
+    "emailSubject": "Betalingsbevestiging",
+    "statusEmailSubject": "Update servicestatus"
   },
   "payments": {
     "title": "Betalingen",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Meer lezen",
     "setDefaults": "Standaardwaarden"
+  },
+  "notifications": {
+    "title": "Klantmeldingen",
+    "viaSms": "via SMS",
+    "viaEmail": "via e-mail",
+    "justNow": "Zojuist",
+    "minutesAgo": "{count}m geleden",
+    "hoursAgo": "{count}u geleden",
+    "daysAgo": "{count}d geleden"
   }
 }

+ 12 - 1
messages/pl/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Email",
     "share": "Udostępnij",
+    "notify": "Powiadom",
     "delete": "Usuń",
     "unpaid": "Niezapłacone",
     "unsavedChanges": "Niezapisane zmiany",
@@ -163,7 +164,8 @@
     "balanceDue": "Pozostało do Zapłaty",
     "paidBadge": "ZAPŁACONE",
     "entityLabel": "Faktura",
-    "emailSubject": "Potwierdzenie Płatności"
+    "emailSubject": "Potwierdzenie Płatności",
+    "statusEmailSubject": "Aktualizacja statusu serwisu"
   },
   "payments": {
     "title": "Płatności",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Czytaj więcej",
     "setDefaults": "Ustaw domyślne"
+  },
+  "notifications": {
+    "title": "Powiadomienia klienta",
+    "viaSms": "przez SMS",
+    "viaEmail": "przez e-mail",
+    "justNow": "Właśnie teraz",
+    "minutesAgo": "{count}m temu",
+    "hoursAgo": "{count}g temu",
+    "daysAgo": "{count}d temu"
   }
 }

+ 12 - 1
messages/pt-BR/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Email",
     "share": "Compartilhar",
+    "notify": "Notificar",
     "delete": "Deletar",
     "unpaid": "Não Pago",
     "unsavedChanges": "Alterações não salvas",
@@ -163,7 +164,8 @@
     "balanceDue": "Saldo Devedor",
     "paidBadge": "PAGO",
     "entityLabel": "Fatura",
-    "emailSubject": "Confirmação de Pagamento"
+    "emailSubject": "Confirmação de Pagamento",
+    "statusEmailSubject": "Atualização do status do serviço"
   },
   "payments": {
     "title": "Pagamentos",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Saiba mais",
     "setDefaults": "Valores padrão"
+  },
+  "notifications": {
+    "title": "Notificações ao cliente",
+    "viaSms": "via SMS",
+    "viaEmail": "via e-mail",
+    "justNow": "Agora mesmo",
+    "minutesAgo": "{count}m atrás",
+    "hoursAgo": "{count}h atrás",
+    "daysAgo": "{count}d atrás"
   }
 }

+ 12 - 1
messages/ru/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "Эл. почта",
     "share": "Поделиться",
+    "notify": "Уведомить",
     "delete": "Удалить",
     "unpaid": "Не оплачено",
     "unsavedChanges": "Несохранённые изменения",
@@ -163,7 +164,8 @@
     "balanceDue": "К оплате",
     "paidBadge": "ОПЛАЧЕНО",
     "entityLabel": "Счёт",
-    "emailSubject": "Подтверждение оплаты"
+    "emailSubject": "Подтверждение оплаты",
+    "statusEmailSubject": "Обновление статуса обслуживания"
   },
   "payments": {
     "title": "Платежи",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Подробнее",
     "setDefaults": "Настройки по умолчанию"
+  },
+  "notifications": {
+    "title": "Уведомления клиента",
+    "viaSms": "через SMS",
+    "viaEmail": "по эл. почте",
+    "justNow": "Только что",
+    "minutesAgo": "{count}м назад",
+    "hoursAgo": "{count}ч назад",
+    "daysAgo": "{count}д назад"
   }
 }

+ 12 - 1
messages/tr/service.json

@@ -27,6 +27,7 @@
     "pdf": "PDF",
     "email": "E-posta",
     "share": "Paylaş",
+    "notify": "Bildir",
     "delete": "Sil",
     "unpaid": "Ödenmedi",
     "unsavedChanges": "Kaydedilmemiş değişiklikler",
@@ -163,7 +164,8 @@
     "balanceDue": "Kalan Bakiye",
     "paidBadge": "ÖDENDİ",
     "entityLabel": "Fatura",
-    "emailSubject": "Ödeme Onayı"
+    "emailSubject": "Ödeme Onayı",
+    "statusEmailSubject": "Servis durumu güncellemesi"
   },
   "payments": {
     "title": "Ödemeler",
@@ -317,5 +319,14 @@
     "customTechnicians": "Custom",
     "readMore": "Daha fazla",
     "setDefaults": "Varsayılanlar"
+  },
+  "notifications": {
+    "title": "Müşteri bildirimleri",
+    "viaSms": "SMS ile",
+    "viaEmail": "e-posta ile",
+    "justNow": "Az önce",
+    "minutesAgo": "{count}dk önce",
+    "hoursAgo": "{count}sa önce",
+    "daysAgo": "{count}g önce"
   }
 }

+ 9 - 0
src/app/(authenticated)/vehicles/[id]/service/[serviceId]/page.tsx

@@ -177,6 +177,14 @@ export default async function ServiceDetailPage({
     .filter((a) => a.category === "document" || a.category === "diagnostic")
     .map((a) => ({ ...a, includeInInvoice: a.includeInInvoice ?? true }));
 
+  // Fetch notification history for this service record
+  const notificationHistory = await db.smsMessage.findMany({
+    where: { relatedEntityId: serviceId, relatedEntityType: "service-record", direction: "outbound" },
+    select: { id: true, body: true, status: true, createdAt: true, toNumber: true },
+    orderBy: { createdAt: "desc" },
+    take: 10,
+  });
+
   // Fetch open observations for this vehicle (not just this service)
   const openObservations = await db.vehicleFinding.findMany({
     where: { vehicleId: id, status: { not: "resolved" } },
@@ -218,6 +226,7 @@ export default async function ServiceDetailPage({
         statusReports={(statusReportsResult.success && statusReportsResult.data ? statusReportsResult.data : []).map(r => ({ ...r, createdAt: r.createdAt.toISOString(), expiresAt: r.expiresAt?.toISOString() || null, feedbackAt: r.feedbackAt?.toISOString() || null, sentAt: r.sentAt?.toISOString() || null }))}
         findings={findingsResult.success && findingsResult.data ? findingsResult.data : []}
         openObservations={openObservations}
+        notificationHistory={notificationHistory.map(n => ({ ...n, createdAt: n.createdAt.toISOString() }))}
       />
     </div>
   );

+ 6 - 1
src/components/notify-customer-dialog.tsx

@@ -1,6 +1,6 @@
 "use client";
 
-import { useState } from "react";
+import { useState, useEffect } from "react";
 import {
   Dialog,
   DialogContent,
@@ -55,6 +55,11 @@ export function NotifyCustomerDialog({
   const hasPhone = !!customer.phone;
   const hasEmail = !!customer.email;
 
+  // Sync message when defaultMessage prop changes (e.g. async template load)
+  useEffect(() => {
+    if (defaultMessage) setMessage(defaultMessage);
+  }, [defaultMessage]);
+
   // Reset state when dialog opens with new message
   const handleOpenChange = (next: boolean) => {
     if (next) {

+ 39 - 0
src/features/vehicles/Components/service-page/DetailsRightColumn.tsx

@@ -1,4 +1,6 @@
 import { useRouter } from 'next/navigation'
+import { useTranslations } from 'next-intl'
+import { MessageSquare } from 'lucide-react'
 import { SharedLinkCard } from '@/components/shared-link-card'
 import { InvoiceDetailsSection } from '../service-edit/InvoiceDetailsSection'
 import { BasicInfoSection } from '../service-edit/BasicInfoSection'
@@ -23,6 +25,7 @@ interface DetailsRightColumnProps {
   initialVehicle: { id: string; make: string; model: string; year: number; licensePlate: string | null }
   boardTechnicians: BoardTechnicianOption[]
   orgMembers?: OrgMemberOption[]
+  notificationHistory?: { id: string; body: string; status: string; createdAt: string; toNumber: string }[]
 }
 
 export function DetailsRightColumn({
@@ -36,6 +39,7 @@ export function DetailsRightColumn({
   initialVehicle,
   boardTechnicians,
   orgMembers,
+  notificationHistory = [],
 }: DetailsRightColumnProps) {
   const router = useRouter()
 
@@ -114,6 +118,41 @@ export function DetailsRightColumn({
         onValuesReady={formState.onCustomFieldsReady}
         onChange={formState.markDirty}
       />
+      {notificationHistory.length > 0 && (
+        <NotificationHistory notifications={notificationHistory} />
+      )}
+    </div>
+  )
+}
+
+function NotificationHistory({ notifications }: { notifications: { id: string; body: string; status: string; createdAt: string; toNumber: string }[] }) {
+  const t = useTranslations('service.notifications')
+
+  function timeAgo(dateStr: string) {
+    const diff = Date.now() - new Date(dateStr).getTime()
+    const mins = Math.floor(diff / 60000)
+    if (mins < 1) return t('justNow')
+    if (mins < 60) return t('minutesAgo', { count: mins })
+    const hours = Math.floor(mins / 60)
+    if (hours < 24) return t('hoursAgo', { count: hours })
+    const days = Math.floor(hours / 24)
+    return t('daysAgo', { count: days })
+  }
+
+  return (
+    <div className="rounded-lg border p-3">
+      <div className="mb-2 flex items-center gap-2">
+        <MessageSquare className="h-3.5 w-3.5 text-muted-foreground" />
+        <h3 className="text-sm font-semibold">{t('title')}</h3>
+      </div>
+      <div className="space-y-2">
+        {notifications.map((n) => (
+          <div key={n.id} className="text-xs">
+            <p className="text-muted-foreground">{timeAgo(n.createdAt)} · {t('viaSms')}</p>
+            <p className="mt-0.5 truncate">{n.body}</p>
+          </div>
+        ))}
+      </div>
     </div>
   )
 }

+ 57 - 11
src/features/vehicles/Components/service-page/ServicePageClient.tsx

@@ -29,6 +29,9 @@ import {
 import { CalendarIcon, AlertTriangle } from 'lucide-react'
 import { deleteFinding } from '@/features/vehicles/Actions/findingActions'
 import { updateServiceRecord } from '@/features/vehicles/Actions/serviceActions'
+import { getSmsTemplates } from '@/features/sms/Actions/smsActions'
+import { SMS_TEMPLATE_DEFAULTS, interpolateSmsTemplate } from '@/lib/sms-templates'
+import { SETTING_KEYS } from '@/features/settings/Schema/settingsSchema'
 import { LaborPresetPickerDialog } from '@/features/labor-presets/Components/LaborPresetPickerDialog'
 import type { LaborPresetOption } from './service-page-types'
 import { ServiceImagesManager } from '../service-images-manager'
@@ -77,6 +80,7 @@ export function ServicePageClient({
   initialTab,
   findings = [],
   openObservations = [],
+  notificationHistory = [],
 }: ServicePageClientProps) {
   const t = useTranslations('service')
   const router = useRouter()
@@ -156,6 +160,32 @@ export function ServicePageClient({
 
   useHardwareScanner({ onScan: handleBarcodeScan, enabled: activeTab === 'details' })
 
+  // Notify customer
+  const statusTemplateKeys: Record<string, string> = {
+    'in-progress': SETTING_KEYS.SMS_TEMPLATE_STATUS_IN_PROGRESS,
+    'waiting-parts': SETTING_KEYS.SMS_TEMPLATE_STATUS_WAITING_PARTS,
+    'completed': SETTING_KEYS.SMS_TEMPLATE_STATUS_READY,
+  }
+  const [showNotifyDialog, setShowNotifyDialog] = useState(false)
+  const [notifyMessage, setNotifyMessage] = useState('')
+
+  const handleNotifyCustomer = useCallback(async () => {
+    if (!record.vehicle.customer) return
+    const templateKey = statusTemplateKeys[formState.status] || SETTING_KEYS.SMS_TEMPLATE_STATUS_READY
+    const tplResult = await getSmsTemplates()
+    const tplData = tplResult.success && tplResult.data ? tplResult.data : null
+    const tpl = tplData?.templates[templateKey] || SMS_TEMPLATE_DEFAULTS[templateKey] || ''
+    const vehicle = `${record.vehicle.year} ${record.vehicle.make} ${record.vehicle.model}`
+    const message = interpolateSmsTemplate(tpl, {
+      customer_name: record.vehicle.customer.name,
+      vehicle,
+      company_name: tplData?.companyName || '',
+      current_user: tplData?.currentUser || '',
+    })
+    setNotifyMessage(message)
+    setShowNotifyDialog(true)
+  }, [formState.status, record.vehicle.customer, record.vehicle.year, record.vehicle.make, record.vehicle.model]) // eslint-disable-line react-hooks/exhaustive-deps
+
   // Observations state
   const tf = useTranslations('vehicles.findings')
   const [addingObservations, setAddingObservations] = useState(false)
@@ -223,6 +253,8 @@ export function ServicePageClient({
         onDelete={actions.handleDelete}
         onShowEmail={async () => { if (!await checkDates()) return; if (formState.hasUnsavedChanges) await actions.saveNow(); actions.setShowEmailDialog(true) }}
         onShowShare={async () => { if (!await checkDates()) return; if (formState.hasUnsavedChanges) await actions.saveNow(); actions.setShowShareDialog(true) }}
+        onNotifyCustomer={handleNotifyCustomer}
+        hasCustomer={!!record.vehicle.customer}
       />
 
       {activeTab === 'details' && (
@@ -261,6 +293,7 @@ export function ServicePageClient({
                   initialVehicle={initialVehicle}
                   boardTechnicians={boardTechnicians}
                   orgMembers={orgMembers}
+                  notificationHistory={notificationHistory}
                 />
               }
             />
@@ -378,17 +411,30 @@ export function ServicePageClient({
       />
 
       {record.vehicle.customer && (
-        <NotifyCustomerDialog
-          open={actions.showPaymentNotifyDialog}
-          onOpenChange={actions.setShowPaymentNotifyDialog}
-          customer={record.vehicle.customer}
-          defaultMessage={actions.paymentNotifyMessage}
-          emailSubject={t('invoice.emailSubject')}
-          smsEnabled={smsEnabled}
-          emailEnabled={emailEnabled}
-          relatedEntityType="service-record"
-          relatedEntityId={record.id}
-        />
+        <>
+          <NotifyCustomerDialog
+            open={actions.showPaymentNotifyDialog}
+            onOpenChange={actions.setShowPaymentNotifyDialog}
+            customer={record.vehicle.customer}
+            defaultMessage={actions.paymentNotifyMessage}
+            emailSubject={t('invoice.emailSubject')}
+            smsEnabled={smsEnabled}
+            emailEnabled={emailEnabled}
+            relatedEntityType="service-record"
+            relatedEntityId={record.id}
+          />
+          <NotifyCustomerDialog
+            open={showNotifyDialog}
+            onOpenChange={setShowNotifyDialog}
+            customer={record.vehicle.customer}
+            defaultMessage={notifyMessage}
+            emailSubject={t('invoice.statusEmailSubject')}
+            smsEnabled={smsEnabled}
+            emailEnabled={emailEnabled}
+            relatedEntityType="service-record"
+            relatedEntityId={record.id}
+          />
+        </>
       )}
 
       {/* Expired dates check dialog */}

+ 11 - 0
src/features/vehicles/Components/service-page/UnifiedServiceHeader.tsx

@@ -12,6 +12,7 @@ import {
   Globe,
   Loader2,
   Mail,
+  MessageSquare,
   Save,
   Trash2,
 } from 'lucide-react'
@@ -43,6 +44,8 @@ interface UnifiedServiceHeaderProps {
   onDelete: () => void
   onShowEmail: () => void
   onShowShare: () => void
+  onNotifyCustomer?: () => void
+  hasCustomer?: boolean
 }
 
 export function UnifiedServiceHeader({
@@ -62,6 +65,8 @@ export function UnifiedServiceHeader({
   onDelete,
   onShowEmail,
   onShowShare,
+  onNotifyCustomer,
+  hasCustomer = false,
 }: UnifiedServiceHeaderProps) {
   const t = useTranslations('service.header')
   const tabs: { label: string; value: ServiceTab }[] = [
@@ -135,6 +140,12 @@ export function UnifiedServiceHeader({
               <Globe className="h-3.5 w-3.5 sm:mr-1" />
               <span className="hidden sm:inline">{t('share')}</span>
             </Button>
+            {hasCustomer && onNotifyCustomer && (
+              <Button variant="outline" size="sm" onClick={onNotifyCustomer}>
+                <MessageSquare className="h-3.5 w-3.5 sm:mr-1" />
+                <span className="hidden sm:inline">{t('notify')}</span>
+              </Button>
+            )}
             <Button
               variant="outline"
               size="sm"

+ 1 - 0
src/features/vehicles/Components/service-page/service-page-types.ts

@@ -58,6 +58,7 @@ export interface ServicePageClientProps {
   initialTab?: string
   findings?: { id: string; description: string; severity: string; status: string; notes: string | null }[]
   openObservations?: { id: string; description: string; severity: string; notes: string | null; serviceRecordId: string | null }[]
+  notificationHistory?: { id: string; body: string; status: string; createdAt: string; toNumber: string }[]
 }
 
 export type { ServicePartInput, ServiceLaborInput, ServiceDetail, InitialData, InventoryPartOption, LaborPresetOption }