Kulcare Docs
DeveloperBilling

Care Packages Implementation Guide

This document provides a comprehensive reference for implementing the Care Packages feature in the Growth module of the Kulcare platform.

Overview

The Care Packages feature allows clinics to create and manage bundled service packages that patients can subscribe to. These packages include multiple consultations and services at a discounted rate with a specific duration.

Architecture Integration

Module Federation Context

  • Host App: Shell application (port 3000)
  • Remote App: Growth module (port 3003)
  • Route: /growth/packages
  • Base Path Handling: Uses getBasePath() for federated mode

Shared Dependencies

  • @kulcare/ui-kit: Reusable UI components
  • @kulcare/api-client: Centralized API client
  • @kulcare/auth: Authentication and authorization
  • @tanstack/react-query: Server state management

API Specification

1. Get Clinics for User

GET /users/{user_id}/clinics

Response:
{
  success: boolean
  message: string
  data: {
    clinics: Clinic[]
  }
}

2. Get Care Packages

GET /clinics/{clinic_id}/care_packages?page=1&perpage=20&c_grp=true

Response:
{
  success: boolean
  message: string
  data: {
    care_packages: CarePackage[]
    total_pages: number
    total_records: number
  }
}

Type Definitions

Core Types

// types/carePackages.ts

export interface Clinic {
  id: number
  name: string
  locality: string
  unique_id: string
  speciality: {
    id: number
    name: string
  }
  role: 'owner' | 'consultant' | 'staff'
  profile_name: string
  settings_viewed: boolean
  telemedicine_setup: boolean
  use_kulcare_appts: boolean
  book_appt_link: string
}

export interface CarePackageService {
  id: number
  clinic_service_id: number
  doctor_id: number
  benefits_count: number
  name: string
  price: number
}

export interface CarePackage {
  id: number
  clinic_id: number
  name: string
  price: number
  duration_frequency: number
  duration_unit: 'days' | 'weeks' | 'months'
  doctor_id: number
  expire_by: string
  subscribers_count: number
  short_link: string
  clinic_name: string
  doctor_details: {
    full_name: string
  }
  care_package_services: CarePackageService[]
}

export interface GetPackagesParams {
  page?: number
  perpage?: number
  search?: string
  clinic_id?: number
  c_grp?: boolean
}

export interface PackagesResponse {
  success: boolean
  message: string
  data: {
    care_packages: CarePackage[]
    total_pages: number
    total_records: number
  }
}

File Structure

apps/growth/src/
├── services/
│   └── carePackagesApi.ts        # API service layer
├── types/
│   └── carePackages.ts           # TypeScript definitions
├── pages/
│   └── packages/
│       ├── index.ts              # Export barrel
│       ├── PackagesPage.tsx      # Main container
│       ├── components/
│       │   ├── PackagesTable.tsx # Table component
│       │   ├── PackageActions.tsx # Action buttons
│       │   ├── AddPackageButton.tsx # Create new
│       │   └── PackageFilters.tsx # Search & filters
│       └── hooks/
│           ├── usePackages.ts    # Data fetching
│           └── useClinics.ts     # Clinic data
└── layouts/
    └── GrowthLayout.tsx          # Update navigation

Implementation Details

API Service Layer

// services/carePackagesApi.ts
import { apiClient } from "@kulcare/api-client"
import type { 
  GetPackagesParams, 
  PackagesResponse,
  CarePackage,
  Clinic 
} from "../types/carePackages"

export const carePackagesApi = {
  async getClinics(userId: string): Promise<Clinic[]> {
    if (import.meta.env.VITE_USE_REAL_API === 'true') {
      const response = await apiClient.get<{
        success: boolean
        data: { clinics: Clinic[] }
      }>(`/users/${userId}/clinics`)
      
      return response.data.data.clinics
    }
    
    // Mock data for development
    return mockClinics
  },

  async getPackages(
    clinicId: string, 
    params?: GetPackagesParams
  ): Promise<PackagesResponse['data']> {
    if (import.meta.env.VITE_USE_REAL_API === 'true') {
      const response = await apiClient.get<PackagesResponse>(
        `/clinics/${clinicId}/care_packages`,
        { 
          params: {
            page: params?.page || 1,
            perpage: params?.perpage || 20,
            c_grp: true,
            ...params
          }
        }
      )
      
      return response.data.data
    }
    
    // Mock implementation
    return getMockPackages(params)
  },

  async createPackage(clinicId: string, data: CreatePackageData) {
    // Implementation
  },

  async updatePackage(packageId: string, data: UpdatePackageData) {
    // Implementation
  },

  async deletePackage(packageId: string) {
    // Implementation
  }
}

React Query Hooks

// hooks/usePackages.ts
import { useQuery } from "@tanstack/react-query"
import { carePackagesApi } from "../services/carePackagesApi"
import { useAuth } from "@kulcare/auth"

export function usePackages(params?: GetPackagesParams) {
  const { selectedClinic } = useAuth()
  
  return useQuery({
    queryKey: ['packages', selectedClinic?.id, params],
    queryFn: () => carePackagesApi.getPackages(
      selectedClinic!.id.toString(), 
      params
    ),
    enabled: !!selectedClinic,
    staleTime: 5 * 60 * 1000, // 5 minutes
  })
}

export function useClinics() {
  const { user } = useAuth()
  
  return useQuery({
    queryKey: ['clinics', user?.id],
    queryFn: () => carePackagesApi.getClinics(user!.id.toString()),
    enabled: !!user,
    staleTime: 30 * 60 * 1000, // 30 minutes
  })
}

Main Component Structure

// PackagesPage.tsx
import { useState } from "react"
import { usePackages } from "./hooks/usePackages"
import { PackagesTable } from "./components/PackagesTable"
import { PackageFilters } from "./components/PackageFilters"
import { AddPackageButton } from "./components/AddPackageButton"
import { GrowthPageWrapper } from "@/components/GrowthPageWrapper"

export function PackagesPage() {
  const [searchTerm, setSearchTerm] = useState("")
  const [selectedClinic, setSelectedClinic] = useState<string>()
  const [currentPage, setCurrentPage] = useState(1)
  
  const { data, isLoading, error } = usePackages({
    page: currentPage,
    perpage: 20,
    search: searchTerm,
    clinic_id: selectedClinic ? parseInt(selectedClinic) : undefined
  })
  
  return (
    <GrowthPageWrapper>
      <div className="space-y-4">
        <div className="flex justify-between items-center">
          <h1 className="text-2xl font-semibold">Packages</h1>
          <AddPackageButton />
        </div>
        
        <PackageFilters
          searchTerm={searchTerm}
          onSearchChange={setSearchTerm}
          selectedClinic={selectedClinic}
          onClinicChange={setSelectedClinic}
        />
        
        <PackagesTable
          packages={data?.care_packages || []}
          isLoading={isLoading}
          error={error}
          pagination={{
            currentPage,
            totalPages: data?.total_pages || 1,
            totalRecords: data?.total_records || 0,
            onPageChange: setCurrentPage
          }}
        />
      </div>
    </GrowthPageWrapper>
  )
}

Table Component

// components/PackagesTable.tsx
import { 
  Table, 
  TableBody, 
  TableCell, 
  TableHead, 
  TableHeader, 
  TableRow 
} from "@kulcare/ui-kit"
import { formatCurrency, formatDate } from "@/lib/utils"
import { PackageActions } from "./PackageActions"
import type { CarePackage } from "../types/carePackages"

interface PackagesTableProps {
  packages: CarePackage[]
  isLoading: boolean
  error: Error | null
  pagination: {
    currentPage: number
    totalPages: number
    totalRecords: number
    onPageChange: (page: number) => void
  }
}

export function PackagesTable({ 
  packages, 
  isLoading, 
  error, 
  pagination 
}: PackagesTableProps) {
  if (isLoading) {
    return <div>Loading...</div>
  }
  
  if (error) {
    return <div>Error: {error.message}</div>
  }
  
  if (packages.length === 0) {
    return <EmptyState />
  }
  
  return (
    <>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>Package name</TableHead>
            <TableHead>Price</TableHead>
            <TableHead>Duration</TableHead>
            <TableHead>Benefits</TableHead>
            <TableHead>Clinic name</TableHead>
            <TableHead>Subs</TableHead>
            <TableHead>Expires by</TableHead>
            <TableHead>Action</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {packages.map((pkg) => (
            <TableRow key={pkg.id}>
              <TableCell>{pkg.name}</TableCell>
              <TableCell>{formatCurrency(pkg.price)}</TableCell>
              <TableCell>
                {pkg.duration_frequency} {pkg.duration_unit}
              </TableCell>
              <TableCell>
                {pkg.care_package_services.map(s => (
                  <div key={s.id}>
                    {s.benefits_count} {s.name}
                  </div>
                ))}
              </TableCell>
              <TableCell>{pkg.clinic_name}</TableCell>
              <TableCell>{pkg.subscribers_count}</TableCell>
              <TableCell>{formatDate(pkg.expire_by)}</TableCell>
              <TableCell>
                <PackageActions package={pkg} />
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
      
      <Pagination {...pagination} />
    </>
  )
}

Routing Configuration

Update Growth App Routes

// App.tsx
import { PackagesPage } from "./pages/packages"

function GrowthRoutes() {
  return (
    <Routes>
      {/* ... existing routes ... */}
      <Route path="packages" element={<PackagesPage />} />
    </Routes>
  )
}

Update Navigation Menu

// layouts/GrowthLayout.tsx
const menuItems = [
  // ... existing items ...
  {
    key: '/packages',
    icon: <Package />,
    label: 'Packages',
    path: '/packages'
  }
]

UI Components from UI Kit

Reusable Components

  • Table: Already supports density modes
  • Input: For search functionality
  • Select: For clinic filter
  • Button: For actions and create
  • Badge: For counts and status
  • EmptyState: When no packages
  • LoadingState: During data fetch
  • Pagination: For table navigation
  • Dialog/Modal: For create/edit forms
  • Tooltip: For additional info

State Management

Server State (React Query)

  • Package list with pagination
  • Clinic list for filters
  • Optimistic updates for actions
  • Cache invalidation on mutations

Local State

  • Search term (with debounce)
  • Selected clinic filter
  • Current page
  • Table density preference
  • Modal open states

Mock Data Structure

const mockPackages: CarePackage[] = [
  {
    id: 1,
    clinic_id: 204745,
    name: "Premium Health Package",
    price: 5000,
    duration_frequency: 3,
    duration_unit: "months",
    doctor_id: 87009,
    expire_by: "2025-12-31",
    subscribers_count: 15,
    short_link: "http://ps.kulcare.com?s=ABC123",
    clinic_name: "Care for you",
    doctor_details: {
      full_name: "Dr. Madhav Singh"
    },
    care_package_services: [
      {
        id: 1,
        clinic_service_id: 5500,
        doctor_id: 87009,
        benefits_count: 4,
        name: "General Consultation",
        price: 1000
      },
      {
        id: 2,
        clinic_service_id: 5501,
        doctor_id: 87009,
        benefits_count: 2,
        name: "Specialist Consultation",
        price: 1500
      }
    ]
  }
  // ... more mock data
]

Error Handling

API Errors

  • Network failures
  • Authentication errors
  • Validation errors
  • Server errors

UI Error States

  • Empty states with CTAs
  • Error boundaries
  • Toast notifications
  • Inline error messages

Performance Considerations

Optimization Strategies

  • Debounced search (300ms)
  • Pagination (20 items default)
  • React Query caching
  • Lazy loading for modals
  • Virtual scrolling for large lists

Bundle Size

  • Code splitting by route
  • Dynamic imports for forms
  • Tree shaking unused UI components

Testing Checklist

Functionality

  • List packages with pagination
  • Search packages by name
  • Filter by clinic
  • Sort columns
  • Create new package
  • Edit existing package
  • Delete package
  • View package details

Integration

  • Works in standalone mode
  • Works in federated mode
  • Correct base path handling
  • API integration
  • Mock data toggle

User Experience

  • Loading states
  • Error handling
  • Empty states
  • Responsive design
  • Keyboard navigation
  • Screen reader support

Security Considerations

Authorization

  • Clinic-based access control
  • Role-based permissions
  • Subscription status checks

Data Security

  • No sensitive data in URLs
  • Proper error messages
  • Input validation
  • XSS prevention

Future Enhancements

Phase 2 Features

  • Bulk operations
  • Export to CSV/PDF
  • Advanced filters
  • Package templates
  • Analytics dashboard
  • Email notifications
  • Package duplication
  • Version history

Technical Improvements

  • Offline support
  • Real-time updates
  • Advanced caching
  • Performance metrics
  • A/B testing

Development Workflow

Getting Started

# Start development servers
pnpm dev:all

# Navigate to Growth module
http://localhost:3003/packages (standalone)
http://localhost:3000/growth/packages (federated)

Environment Variables

VITE_USE_REAL_API=false  # Use mock data
VITE_API_URL=https://api-stg.kulcare.com

Git Workflow

# Create feature branch
git checkout -b feature/care-packages

# Commit changes
git add .
git commit -m "feat(growth): implement care packages listing"

# Push and create PR
git push origin feature/care-packages

References


Last updated: 2025-07-30

On this page