Notification Drawer

Form component for scheduling SMS and push notifications to organizations

Live Example

Create Notification - Send Now

Fill in the form to create a notification that will be sent immediately. Select an organization, enter title and message body.

2:50 PM
Wednesday, April 1
2:50 PM
My Appnow
Notification Title
Notification message will appear here...

Schedule for Later

Toggle to 'Schedule for Later' to see timezone, date, and time selectors. The component includes smart time validation that prevents scheduling in the past.

💡 Tip:Switch to "Schedule for Later" to see the date/time pickers. Times are rounded to 15-minute intervals, and you cannot schedule in the past.

2:50 PM
Wednesday, April 1
2:50 PM
My Appnow
Notification Title
Notification message will appear here...

With iPhone Preview

Set showPreview={true} to display a live iPhone notification preview that updates in real-time as you type.

2:50 PM
Wednesday, April 1
2:50 PM
My Appnow
Notification Title
Notification message will appear here...

Features Overview

The NotificationDrawer component includes many smart features for scheduling notifications.

Send Timing Options:

  • Send Now: Immediate delivery
  • Schedule for Later: Choose date, time, and timezone

Smart Features:

  • Auto-detects browser timezone
  • 15-minute time intervals
  • Prevents past scheduling
  • Automatic project resolution from organization
  • UUID schedule ID generation
  • Real-time validation

Validation:

  • Organization required
  • Title and body required (non-empty)
  • Date and time required for scheduled delivery
  • Project must resolve from organization

Props:

  • api - SGERP API instance (required)
  • showPreview - Show iPhone preview (default: false)
  • onSuccess - Called when notification is created
  • onCancel - Called when user cancels

Notification Drawer

A comprehensive form component for creating and scheduling SMS messages and push notifications to organizations. Supports immediate sending or scheduled delivery with timezone awareness and smart time validation.

Features

  • Dual Notification Types: Supports both SMS messages and push notifications (despite legacy smsscheduled naming)
  • Send Timing Options: Send immediately or schedule for later
  • Timezone Support: Auto-detects browser timezone with option to customize
  • Smart Time Validation: Prevents scheduling in the past, rounds to 15-minute intervals
  • Organization Selection: Autocomplete dropdown for organization selection
  • Project Resolution: Automatically finds associated project from organization
  • Date/Time Pickers: Calendar and time selection with validation
  • UUID Generation: Automatic schedule ID generation
  • Dual-API Pattern: Writes to Tastypie API, refreshes from GET API
  • Collection Integration: Uses prepend() to add new notifications at top of list
  • Full Localization: English and Japanese translation support
  • Loading States: Visual feedback during save operation
  • Error Handling: Comprehensive validation and error messages

Usage

Basic Usage

import { NotificationDrawer } from '@/components/sgerp/editors/notification-drawer';
import { useSGERP } from 'sgerp-frontend-lib';

function MyComponent() {
  const api = useSGERP();

  return (
    <NotificationDrawer
      api={api}
      onSuccess={(notification) => {
        console.log('Notification created:', notification);
      }}
      onCancel={() => {
        console.log('Cancelled');
      }}
    />
  );
}

In a Drawer/Sheet

import { NotificationDrawer } from '@/components/sgerp/editors/notification-drawer';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
import { useSGERP } from 'sgerp-frontend-lib';
import { useState } from 'react';

function MyComponent() {
  const api = useSGERP();
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>
        Create Notification
      </button>

      <Sheet open={open} onOpenChange={setOpen}>
        <SheetContent>
          <SheetHeader>
            <SheetTitle>Schedule Notification</SheetTitle>
          </SheetHeader>
          <div className="mt-6">
            <NotificationDrawer
              api={api}
              onSuccess={(notification) => {
                console.log('Created:', notification);
                setOpen(false);
              }}
              onCancel={() => setOpen(false)}
            />
          </div>
        </SheetContent>
      </Sheet>
    </>
  );
}

With Preview Callbacks

The drawer provides callbacks for real-time preview of notification content:

import { NotificationDrawer } from '@/components/sgerp/editors/notification-drawer';
import { useSGERP } from 'sgerp-frontend-lib';
import { useState } from 'react';

function MyComponent() {
  const api = useSGERP();
  const [previewTitle, setPreviewTitle] = useState('');
  const [previewBody, setPreviewBody] = useState('');
  const [sendTiming, setSendTiming] = useState<'now' | 'later'>('now');

  return (
    <div className="grid grid-cols-2 gap-6">
      {/* Form */}
      <NotificationDrawer
        api={api}
        onTitleChange={setPreviewTitle}
        onBodyChange={setPreviewBody}
        onSendTimingChange={setSendTiming}
        onSuccess={(notification) => {
          console.log('Created:', notification);
        }}
      />

      {/* Preview */}
      <div className="border rounded-lg p-4">
        <h3 className="font-medium mb-2">Preview</h3>
        <div className="space-y-2">
          <div>
            <strong>Title:</strong> {previewTitle || '(empty)'}
          </div>
          <div>
            <strong>Body:</strong> {previewBody || '(empty)'}
          </div>
          <div>
            <strong>Send:</strong> {sendTiming === 'now' ? 'Immediately' : 'Scheduled'}
          </div>
        </div>
      </div>
    </div>
  );
}

Props

PropTypeRequiredDescription
apiReturnType<typeof useSGERP>NoShared API instance (recommended for collection updates)
onSuccess(notification: SMSScheduled) => voidNoCallback when notification is created successfully
onCancel() => voidNoCallback when user cancels
onTitleChange(title: string) => voidNoCallback when title changes (for live preview)
onBodyChange(body: string) => voidNoCallback when body changes (for live preview)
onSendTimingChange(timing: 'now' | 'later') => voidNoCallback when send timing changes
onDateChange(date: Date | undefined) => voidNoCallback when date changes
onTimeChange(time: string) => voidNoCallback when time changes

Form Fields

Required Fields

  • Organization: Organization to send notification to (autocomplete dropdown)
  • Title: Notification title (string)
  • Body: Notification message content (textarea)
  • Send Timing: "Send Now" or "Schedule for Later" (toggle)

Optional Fields

  • URL: Optional URL link for notification (string)

Conditional Fields (When "Schedule for Later" is selected)

  • Timezone: Timezone for scheduling (auto-detected, customizable)
  • Date: Date to send notification (calendar picker)
  • Time: Time to send notification (15-minute intervals)

Send Timing Options

Send Now

  • Sends notification immediately
  • Sets scheduled_at to current timestamp
  • No date/time selection required

Schedule for Later

  • Displays timezone, date, and time selectors
  • Smart validation prevents past scheduling
  • Time rounded to 15-minute intervals
  • Auto-clears invalid times when date changes

Smart Time Validation

The component includes intelligent time validation:

Minimum Time Calculation

  • If today is selected: minimum time is current time + 15 minutes (rounded)
  • If future date is selected: no minimum time restriction
  • Times before minimum are automatically cleared

15-Minute Intervals

  • Time picker shows only 15-minute intervals (e.g., 09:00, 09:15, 09:30)
  • Ensures consistent scheduling

Example

Current time: 14:37
Minimum time: 14:45 (rounded up to next 15-min interval)
Available times: 14:45, 15:00, 15:15, 15:30, ...

Timezone Handling

Auto-Detection

  • Browser timezone automatically detected on component mount
  • Uses Intl.DateTimeFormat().resolvedOptions().timeZone

Customization

  • User can select different timezone from dropdown
  • Affects how scheduled time is interpreted

Example

Browser timezone: America/New_York
Selected date/time: 2025-10-20 15:00
Scheduled: 2025-10-20T15:00:00 in America/New_York timezone

Project Resolution

The component automatically resolves the project ID from the selected organization:

  1. User selects organization
  2. Component fetches organizationproject records
  3. Finds record with type: 'main'
  4. Extracts project_id for API submission

This happens automatically in the background.

Dual-API Implementation

Follows the dual-API pattern:

  1. Write: POST to /api/v2/smsscheduled/ (Tastypie API)

    {
      title: "Notification Title",
      content: "Message body",
      url: "https://example.com",
      message_delivery_type: "push",
      recipient: "organization_code",
      recipient_type: "organization",
      project: "/api/v2/project/123/",
      scheduled_at: "2025-10-20T15:00:00Z",
      timezone: "America/New_York",
      schedule_id: "uuid-v4-string"
    }
    
  2. Refresh: GET from /api/v2/microservices/get?model=smsscheduled&id={id}

  3. Collection Update: Uses prepend() to add notification at top of list

    api.collections.smsscheduled.prepend([freshData]);
    

UUID Generation

Each notification gets a unique schedule_id (UUID v4):

  • Generated client-side using random number generation
  • Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  • Used for tracking and deduplication

Validation

Client-Side Validation

  • Organization required
  • Title required (non-empty)
  • Body required (non-empty)
  • If "later": date and time required
  • Project ID must resolve from organization

Error Messages

All validation errors display in localized error banner:

  • "Please select an organization"
  • "Title is required"
  • "Message body is required"
  • "Please select date and time"
  • "Project not found for organization"

Localization

Uses localization keys from sgerplib/locales/:

Form Labels

  • notification.organization_label / notification.organization_placeholder
  • notification.title_label / notification.title_placeholder
  • notification.body_label / notification.body_placeholder
  • notification.url_label / notification.url_placeholder
  • notification.send_timing_label
  • notification.send_now / notification.send_later
  • notification.timezone_label / notification.timezone_placeholder
  • notification.date_label / notification.date_placeholder
  • notification.time_label / notification.time_placeholder

Buttons and Messages

  • notification.create_button / notification.creating
  • common.cancel

Error Messages

  • notification.error_select_organization
  • notification.error_title_required
  • notification.error_body_required
  • notification.error_datetime_required
  • notification.error_project_required
  • notification.error_create_failed
  • notification.error_refresh_failed

Styling

Built with:

  • Tailwind CSS for all styling
  • shadcn/ui components (Button, Calendar, Popover, ToggleGroup)
  • Lucide React icons (CalendarIcon)
  • Theme-aware colors (works with light/dark themes)

Example: Complete Notifications Page

import { useSGERP } from 'sgerp-frontend-lib';
import { SMSScheduledTable } from '@/components/sgerp/tables/smsscheduled-table';
import { NotificationDrawer } from '@/components/sgerp/editors/notification-drawer';
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet';
import { Button } from '@/components/ui/button';
import { useState } from 'react';
import { Plus } from 'lucide-react';

export default function NotificationsPage() {
  const api = useSGERP();
  const [drawerOpen, setDrawerOpen] = useState(false);

  return (
    <div className="flex-1 flex flex-col gap-6 min-h-0">
      <div className="flex items-start justify-between">
        <div>
          <h1 className="text-3xl font-bold">Notifications</h1>
          <p className="text-muted-foreground mt-2">
            Schedule SMS and push notifications
          </p>
        </div>
        <Button onClick={() => setDrawerOpen(true)}>
          <Plus className="h-4 w-4 mr-2" />
          Create Notification
        </Button>
      </div>

      <div className="flex-1 min-h-0">
        <SMSScheduledTable
          collection={api?.collections.smsscheduled ?? null}
          fullscreenHeight
        />
      </div>

      <Sheet open={drawerOpen} onOpenChange={setDrawerOpen}>
        <SheetContent>
          <SheetHeader>
            <SheetTitle>Schedule Notification</SheetTitle>
          </SheetHeader>
          <div className="mt-6">
            <NotificationDrawer
              api={api}
              onSuccess={(notification) => {
                console.log('Created:', notification);
                setDrawerOpen(false);
              }}
              onCancel={() => setDrawerOpen(false)}
            />
          </div>
        </SheetContent>
      </Sheet>
    </div>
  );
}

Dependencies

Required packages:

  • react (18+)
  • sgerp-frontend-lib (collections, hooks, types)
  • lucide-react (icons)

Components used:

  • @/components/ui/button (shadcn/ui)
  • @/components/ui/calendar (shadcn/ui)
  • @/components/ui/popover (shadcn/ui)
  • @/components/ui/toggle-group (shadcn/ui)
  • SGERPAutocomplete from sgerp-frontend-lib
  • TimezoneSelect from sgerp-frontend-lib
  • TimeSelect from sgerp-frontend-lib