@phygitallabs/survey
v6.3.1
Published
A comprehensive survey management system built with SurveyJS, React, TypeScript, and PostgreSQL, designed for enterprise-grade survey creation, administration, and analytics.
Readme
Survey Package
A comprehensive survey management system built with SurveyJS, React, TypeScript, and PostgreSQL, designed for enterprise-grade survey creation, administration, and analytics.
Features
- 🏗️ Full-Stack Architecture: Complete solution with PostgreSQL database, tRPC API, and React components
- 🎨 Modern UI: Built with SurveyJS and Ant Design v5 for professional, responsive interfaces
- 📊 Advanced Analytics: Real-time survey analytics with comprehensive statistics
- 🔒 Security: JWT-based authentication with admin role management
- 🏢 Multi-tenancy: Project-based survey organization with flexible labeling
- ⚡ Type-Safe: End-to-end TypeScript with Zod validation and Drizzle ORM
- 🔄 Real-time Updates: Live response tracking and instant analytics generation
- 📱 Responsive Design: Works seamlessly on desktop, tablet, and mobile devices
- 🎯 Survey Management: Complete CRUD operations with pagination and filtering
- 📋 Import/Export: JSON-based survey definitions with validation
- 🔍 Results Visualization: Professional analytics dashboard with detailed insights
Installation
# From the root of the monorepo
cd packages/survey
npm installProject Structure
packages/survey/
├── 📁 src/
│ ├── 📁 admin/ # Admin UI components
│ │ └── 📁 components/
│ │ ├── SurveyCreator.tsx # Survey creation/editing
│ │ ├── SurveyList.tsx # Survey management interface
│ │ └── SurveyImport.tsx # JSON import with validation
│ │
│ ├── 📁 api/ # Client-side API layer
│ │ └── surveyApi.ts # Survey API abstraction
│ │
│ ├── 📁 client/ # Client-facing components
│ │ ├── 📁 components/
│ │ │ ├── SurveyRenderer.tsx # Survey display and interaction
│ │ │ └── SurveyResults.tsx # Analytics visualization
│ │ └── 📁 hooks/
│ │ └── useSurveySubmission.ts # Survey submission logic
│ │
│ ├── 📁 core/ # Shared core functionality
│ │ ├── 📁 hooks/
│ │ │ └── useSurvey.ts # Main survey management hook
│ │ ├── 📁 types/
│ │ │ └── survey.types.ts # TypeScript interfaces
│ │ └── 📁 utils/
│ │ └── surveyHelpers.ts # Utility functions and analytics
│ │
│ ├── 📁 db/ # Database schema and types
│ │ ├── schema.ts # Drizzle ORM schema definitions
│ │ └── index.ts # Database exports
│ │
│ ├── 📁 server/ # Server-side tRPC implementation
│ │ ├── 📁 routers/
│ │ │ ├── survey.ts # Survey CRUD operations
│ │ │ ├── response.ts # Response management
│ │ │ └── analytics.ts # Analytics generation
│ │ ├── 📁 middleware/
│ │ │ └── admin.ts # Admin authentication
│ │ ├── context.ts # tRPC context setup
│ │ ├── router.ts # Main router configuration
│ │ └── trpc.ts # tRPC configuration
│ │
│ ├── 📁 scripts/ # Database utilities
│ │ └── migrate.ts # Database migration script
│ │
│ └── 📁 themes/ # UI theming
│ └── default.ts # Default SurveyJS theme
│
├── 📁 drizzle/ # Database migrations
│ ├── 📁 meta/ # Migration metadata
│ └── *.sql # Migration files
│
├── 📄 index.ts # Client-side exports
├── 📄 server.ts # Server-side exports
├── 📄 drizzle.config.ts # Database configuration
├── 📄 migrate.ts # Migration runner
├── 📄 package.json # Dependencies and scripts
├── 📄 tsconfig.json # TypeScript configuration
├── 📄 tsup.config.ts # Build configuration
├── 📄 README.md # This documentation
└── 📄 DEVELOPMENT_HISTORY.md # Development historyModule Overview
🎛️ Admin Module (src/admin/)
Components for survey management and administration:
- SurveyCreator: Create and edit surveys using JSON
- SurveyList: Display surveys with admin actions (edit, delete, status toggle)
- SurveyImport: Import surveys with JSON validation and sample templates
👥 Client Module (src/client/)
Components for survey participation and viewing results:
- SurveyRenderer: Render surveys and collect responses
- SurveyResults: Display comprehensive analytics and results
- useSurveySubmission: Hook for handling survey submissions
⚙️ Core Module (src/core/)
Core functionality and utilities:
- useSurvey: Main hook for survey management operations
- survey.types.ts: TypeScript interfaces and type definitions
- surveyHelpers.ts: Utility functions, analytics generation, and validation
🔌 API Layer (src/api/)
Business logic and data operations:
- SurveyApi: Complete API class with all survey operations
- Storage abstraction: Works with any storage provider implementation
- Business logic: Centralized survey management operations
🎨 Themes Module (src/themes/)
Styling and theming configuration:
- default.ts: Default theme settings and customization options
Quick Start
Basic Usage
import { SurveyRenderer, SurveyResults } from '@phygitallabs/survey';
// Render a survey
<SurveyRenderer
surveyId="your-survey-id"
onComplete={(data) => console.log('Survey completed:', data)}
/>
// View survey results
<SurveyResults
surveyId="your-survey-id"
responseCount={10}
responses={responseData}
/>Admin Dashboard
import { SurveyList, SurveyImport } from '@phygitallabs/survey';
// Survey management with modern UI
<SurveyList
surveys={surveys}
onEdit={(id) => handleEdit(id)}
onDelete={(id) => handleDelete(id)}
onStatusChange={(id, isActive) => handleStatusChange(id, isActive)}
onViewResults={(id) => handleViewResults(id)}
isAdminView={true}
/>
// Import surveys with sample templates
<SurveyImport
onImport={handleImport}
sampleSurveyJson={sampleSurvey}
sampleQuizJson={sampleQuiz}
onSuccess={handleSuccess}
/>Components
SurveyRenderer
Renders surveys with Ant Design styling and collects responses.
<SurveyRenderer
surveyId={string}
onComplete={(data: Record<string, any>) => void}
theme?: object
/>SurveyResults
Displays comprehensive survey analytics with charts and statistics.
<SurveyResults
surveyId={string}
responseCount?: number
responses?: SurveyResponse[]
/>SurveyList
Modern list view for survey management with admin/client modes.
<SurveyList
surveys={Survey[]}
onEdit?: (id: string) => void
onDelete?: (id: string) => void
onStatusChange?: (id: string, isActive: boolean) => void
onViewResults?: (id: string) => void
onParticipate?: (id: string) => void
isAdminView?: boolean
autoLoad?: boolean
/>SurveyImport
Import surveys with JSON validation and sample templates.
<SurveyImport
onImport={(json: Record<string, any>) => void}
sampleSurveyJson?: Record<string, any>
sampleQuizJson?: Record<string, any>
onSuccess?: () => void
/>API Reference
Core Hooks
import { useSurvey } from "@phygitallabs/survey";
const {
survey,
responses,
loading,
error,
loadSurvey,
loadAllSurveys,
saveSurvey,
deleteSurvey,
loadResponses,
submitResponse,
getAnalytics,
createSurvey,
validateSurvey,
} = useSurvey(surveyId);API Usage
import { SurveyApi, createSurveyApi, SurveyStorage } from "@phygitallabs/survey";
// Create a custom storage implementation
class MyStorage implements SurveyStorage {
// Implement all required methods
}
// Create API instance
const api = createSurveyApi(new MyStorage());
// Use API directly
const surveys = await api.loadAllSurveys();
const survey = await api.loadSurvey("survey-id");
const id = await api.saveSurvey(surveyData);
// Or use with React hook
const { loadAllSurveys, saveSurvey } = useSurvey(undefined, new MyStorage());
// Demo implementation (IndexedDB) is available in the demo app
// See: apps/tapquest-portal/src/modules/surveys/storage/indexedDB.tsAnalytics Generation
import { generateSurveyAnalytics } from "@phygitallabs/survey";
const analytics = generateSurveyAnalytics(survey, responses);Data Types
Core Types
Survey
interface Survey {
id: string; // UUID primary key
title: string; // Survey title
description?: string; // Survey description
json: Record<string, any>; // SurveyJS JSON configuration
type: string; // Survey type (survey, quiz, etc.)
labels?: Record<string, any>; // Flexible metadata (projectId, tags, etc.)
createdAt: Date; // Creation timestamp
updatedAt: Date; // Last update timestamp
isActive: boolean; // Survey status (active/inactive)
}SurveyResponse
interface SurveyResponse {
id: string; // UUID primary key
surveyId: string; // Foreign key to surveys table
data: Record<string, any>; // Response data (question answers)
submittedAt: Date; // Submission timestamp
userId?: string; // Optional user identifier
labels?: Record<string, any>; // Flexible metadata
deviceUid?: string; // Optional device identifier
}SurveyAnalytics
interface SurveyAnalytics {
id: string; // UUID primary key
surveyId: string; // Foreign key to surveys table
totalResponses: number; // Total number of responses
averageCompletionTime?: number; // Average time to complete (seconds)
completionRate?: number; // Completion rate percentage (0-100)
questionStats?: Record<string, any>; // Per-question statistics JSON
updatedAt: Date; // Last analytics update timestamp
}Question Statistics
QuestionStats
interface QuestionStats {
questionType: string; // Type of question (text, choice, etc.)
totalAnswers: number; // Number of responses to this question
answerDistribution?: Record<string, number>; // Answer choice distribution
averageValue?: number; // Average for numeric questions
minValue?: number; // Minimum value for numeric questions
maxValue?: number; // Maximum value for numeric questions
textResponses?: string[]; // Array of text responses
averageTextLength?: number; // Average length of text responses
}Storage Interface
SurveyStorage
interface SurveyStorage {
// Initialize the storage
init(): Promise<void>;
// Survey operations
getSurvey(id: string): Promise<Survey | null>;
getAllSurveys(): Promise<Survey[]>;
saveSurvey(survey: Survey): Promise<string>;
deleteSurvey(id: string): Promise<void>;
// Response operations
getResponse(id: string): Promise<SurveyResponse | null>;
getResponsesBySurvey(surveyId: string): Promise<SurveyResponse[]>;
saveResponse(response: SurveyResponse): Promise<string>;
deleteResponse(id: string): Promise<void>;
deleteResponsesBySurvey(surveyId: string): Promise<void>;
}API Types
SurveyApi
class SurveyApi {
constructor(storage: SurveyStorage);
// Survey management
loadSurvey(id: string): Promise<Survey | null>;
loadAllSurveys(): Promise<Survey[]>;
saveSurvey(survey: Survey): Promise<string>;
deleteSurvey(id: string): Promise<void>;
createSurvey(
title: string,
description?: string,
json?: Record<string, any>
): Promise<string>;
// Response management
loadResponses(surveyId: string): Promise<SurveyResponse[]>;
submitResponse(surveyId: string, data: Record<string, any>): Promise<string>;
deleteResponse(id: string): Promise<void>;
deleteAllResponses(surveyId: string): Promise<void>;
// Analytics
getAnalytics(surveyId: string): Promise<SurveyAnalytics>;
// Validation
validateSurvey(survey: Survey): boolean;
}Component Props
SurveyRenderer Props
interface SurveyRendererProps {
surveyId: string; // Survey to render
onComplete?: (data: Record<string, any>) => void; // Completion callback
theme?: object; // Custom theme object
}SurveyResults Props
interface SurveyResultsProps {
surveyId: string; // Survey to display results for
responseCount?: number; // Total response count
responses?: SurveyResponse[]; // Response data array
}SurveyList Props
interface SurveyListProps {
surveys: Survey[]; // Array of surveys to display
onEdit?: (id: string) => void; // Edit callback
onDelete?: (id: string) => void; // Delete callback
onStatusChange?: (id: string, isActive: boolean) => void; // Status toggle callback
onViewResults?: (id: string) => void; // View results callback
onParticipate?: (id: string) => void; // Participate callback
isAdminView?: boolean; // Admin vs client view mode
autoLoad?: boolean; // Auto-load surveys
}SurveyImport Props
interface SurveyImportProps {
onImport: (json: Record<string, any>) => void; // Import callback
sampleSurveyJson?: Record<string, any>; // Sample survey template
sampleQuizJson?: Record<string, any>; // Sample quiz template
onSuccess?: () => void; // Success callback
}Hook Return Types
useSurvey Hook
interface UseSurveyReturn {
// State
survey: Survey | null;
responses: SurveyResponse[];
loading: boolean;
error: string | null;
// Survey operations
loadSurvey: (id: string) => Promise<void>;
loadAllSurveys: () => Promise<void>;
saveSurvey: (survey: Survey) => Promise<string>;
deleteSurvey: (id: string) => Promise<void>;
createSurvey: (
title: string,
description?: string,
json?: Record<string, any>
) => Promise<string>;
// Response operations
loadResponses: (surveyId: string) => Promise<void>;
submitResponse: (
surveyId: string,
data: Record<string, any>
) => Promise<string>;
deleteResponse: (id: string) => Promise<void>;
deleteAllResponses: (surveyId: string) => Promise<void>;
// Analytics
getAnalytics: (surveyId: string) => Promise<SurveyAnalytics>;
// Validation
validateSurvey: (survey: Survey) => boolean;
}Utility Types
SurveyJSON
type SurveyJSON = Record<string, any>; // SurveyJS JSON configurationSurveyTheme
interface SurveyTheme {
cssVariables?: Record<string, string>; // CSS custom properties
cssClasses?: Record<string, string>; // CSS class mappings
[key: string]: any; // Additional theme properties
}Demo Implementation
A complete demo is available in apps/tapquest-portal/src/modules/surveys/ showcasing:
- Admin View: Survey management, import/export, analytics
- Client View: Survey participation, response collection
- Real-time Updates: Live response tracking and analytics
- Sample Templates: Pre-built survey and quiz examples
- IndexedDB Storage: Demo storage implementation for local development
Demo Storage Implementation
The demo includes an IndexedDB storage implementation for local development:
// Location: apps/tapquest-portal/src/modules/surveys/storage/indexedDB.ts
import { IndexedDBStorage } from "./storage/indexedDB";
const storage = new IndexedDBStorage();
const { loadAllSurveys, saveSurvey } = useSurvey(undefined, storage);Note: This IndexedDB implementation is for demo purposes only. In production, you should implement your own storage provider using the SurveyStorage interface.
Architecture
The survey package implements a modern full-stack architecture with clear separation of concerns:
🏗️ Full-Stack Architecture
┌─────────────────────────────────────────────────────────────┐
│ Client Applications │
├─────────────────────────────────────────────────────────────┤
│ React Components │ Hooks │ Theme System │ API │
│ - SurveyRenderer │ useSurvey │ - SurveyJS │ Client │
│ - SurveyResults │ useSubmit │ - Ant Design │ Layer │
│ - SurveyList │ │ - CSS Vars │ │
├─────────────────────────────────────────────────────────────┤
│ tRPC API Layer │
│ Type-Safe RPC │ Routers │ Middleware │ Validation │
│ - Auto-complete │ - Survey │ - Auth │ - Zod │
│ - Error types │ - Response │ - Admin │ - Schema │
│ - TypeScript │ - Analytics│ - CORS │ - Runtime │
├─────────────────────────────────────────────────────────────┤
│ Database Layer (PostgreSQL) │
│ ORM Integration │ Schema │ Migrations │ Relations │
│ - Drizzle ORM │ - Surveys │ - Versioned │ - Foreign │
│ - Type Safety │ - Responses│ - Automated │ Keys │
│ - Query Builder │ - Analytics│ - Rollback │ - Cascade │
└─────────────────────────────────────────────────────────────┘🔄 Data Flow
- Client Request: React components call tRPC procedures
- Type Validation: Zod schemas validate input/output
- Authentication: JWT middleware checks permissions
- Database Operations: Drizzle ORM executes type-safe queries
- Response: Type-safe data flows back to components
🔌 API-First Design
The package supports both direct API usage and React integration:
- tRPC Procedures: Type-safe server functions with automatic client generation
- SurveyApi Class: Client-side abstraction for storage-agnostic operations
- React Hooks: State management wrappers around API calls
- Dual Exports: Separate client (
./index) and server (./server) entry points
Key Architectural Benefits
- Type Safety: End-to-end TypeScript with runtime validation
- Scalability: Production-ready PostgreSQL with proper indexing
- Security: JWT authentication, input validation, SQL injection prevention
- Performance: Optimized queries, pagination, connection pooling
- Maintainability: Clear separation of concerns, modular structure
- Testability: Independent layers can be tested in isolation
Features in Detail
🎨 Modern UI Components
- Ant Design v5: Professional, accessible, and responsive components
- Statistics Cards: Visual representation of survey metrics
- Data Tables: Clean, sortable tables for response analysis
- Progress Indicators: Loading states and completion tracking
- Alert System: User-friendly error and success notifications
📊 Advanced Analytics
- Response Distribution: Visual breakdown of answer choices
- Numeric Statistics: Min, max, average, and median calculations
- Text Analysis: Response count and average length metrics
- Completion Tracking: Real-time completion rate monitoring
- Question-level Insights: Detailed statistics per question type
🔄 Real-time Features
- Live Updates: Response counts update immediately after submission
- Instant Analytics: Results generated on-demand with fresh data
- Status Management: Toggle survey active/inactive states
- Response Tracking: Monitor survey participation in real-time
📱 Responsive Design
- Mobile-First: Optimized for all screen sizes
- Touch-Friendly: Proper touch targets and interactions
- Flexible Layout: Adapts to different viewport sizes
- Accessible: WCAG compliant components and interactions
Server Setup
Database Configuration
The package uses PostgreSQL with Drizzle ORM for data persistence:
// Configure your database connection
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(process.env.DATABASE_URL);
const db = drizzle(client);Running Migrations
# Generate migration files
npm run db:generate
# Apply migrations
npm run db:migrate
# Open Drizzle Studio (database GUI)
npm run db:studiotRPC Server Integration
import { appRouter, createContext } from '@phygitallabs/survey/server';
import { createHTTPServer } from '@trpc/server/adapters/standalone';
const server = createHTTPServer({
router: appRouter,
createContext,
});
server.listen(3001);Environment Variables
# Required environment variables
DATABASE_URL=postgresql://user:password@localhost:5432/survey_db
JWT_SECRET=your-jwt-secret-key
ADMIN_JWT_SECRET=your-admin-jwt-secret
# Optional
NODE_ENV=development
PORT=3001Client Setup
tRPC Client Configuration
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '@phygitallabs/survey/server';
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3001/trpc',
}),
],
});
// Usage
const surveys = await trpc.survey.getAll.query({ page: 1, limit: 10 });React Query Integration
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@phygitallabs/survey/server';
const trpc = createTRPCReact<AppRouter>();
const queryClient = new QueryClient();
function App() {
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{/* Your app components */}
</QueryClientProvider>
</trpc.Provider>
);
}Development
See DEVELOPMENT_HISTORY.md for the development roadmap and history.
License
This package uses the free version of SurveyJS, which is licensed under MIT.
