From 6cd90d6ad81dca5798629f3cc299fd888edb3d82 Mon Sep 17 00:00:00 2001 From: Jeremiah Chienda Date: Mon, 25 Nov 2024 22:40:40 +0200 Subject: [PATCH] cell-connect documentation --- app/hooks/use-data-table.ts | 668 +++++++++++++++---------------- docs/DOCUMENTATION.md | 755 ++++++++++++++++++++++++++++++++++++ public/images/hierarchy.svg | 10 + 3 files changed, 1104 insertions(+), 329 deletions(-) create mode 100644 docs/DOCUMENTATION.md create mode 100644 public/images/hierarchy.svg diff --git a/app/hooks/use-data-table.ts b/app/hooks/use-data-table.ts index 393b4af..5045468 100644 --- a/app/hooks/use-data-table.ts +++ b/app/hooks/use-data-table.ts @@ -1,344 +1,354 @@ -"use client" -import { redirect } from "@remix-run/react" -import * as React from "react" +import { redirect } from '@remix-run/react'; +import * as React from 'react'; import { - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, - type ColumnDef, - type ColumnFiltersState, - type PaginationState, - type SortingState, - type VisibilityState, -} from "@tanstack/react-table" -import { z } from "zod" - -import { useDebounce } from "@/hooks/use-debounce" + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + type ColumnDef, + type ColumnFiltersState, + type PaginationState, + type SortingState, + type VisibilityState, +} from '@tanstack/react-table'; +import { z } from 'zod'; + +import { useDebounce } from '@/hooks/use-debounce'; import { useLocation } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom'; -import { DataTableFilterField } from "@/@types" - +import { DataTableFilterField } from '@/@types'; interface UseDataTableProps { - /** - * The data for the table. - * @default [] - * @type TData[] - */ - data: TData[] - - /** - * The columns of the table. - * @default [] - * @type ColumnDef[] - */ - columns: ColumnDef[] - - /** - * The number of pages in the table. - * @type number - */ - pageCount: number - - /** - * The default number of rows per page. - * @default 10 - * @type number | undefined - * @example 20 - */ - defaultPerPage?: number - - /** - * The default sort order. - * @default undefined - * @type `${Extract}.${"asc" | "desc"}` | undefined - * @example "createdAt.desc" - */ - defaultSort?: `${Extract}.${"asc" | "desc"}` - - /** - * Defines filter fields for the table. Supports both dynamic faceted filters and search filters. - * - Faceted filters are rendered when `options` are provided for a filter field. - * - Otherwise, search filters are rendered. - * - * The indie filter field `value` represents the corresponding column name in the database table. - * @default [] - * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: React.ComponentType<{ className?: string }> }[] }[] - * @example - * ```ts - * // Render a search filter - * const filterFields = [ - * { label: "Title", value: "title", placeholder: "Search titles" } - * ]; - * // Render a faceted filter - * const filterFields = [ - * { - * label: "Status", - * value: "status", - * options: [ - * { label: "Todo", value: "todo" }, - * { label: "In Progress", value: "in-progress" }, - * { label: "Done", value: "done" }, - * { label: "Canceled", value: "canceled" } - * ] - * } - * ]; - * ``` - */ - filterFields?: DataTableFilterField[] - - /** - * Enable notion like column filters. - * Advanced filters and column filters cannot be used at the same time. - * @default false - * @type boolean - */ - enableAdvancedFilter?: boolean + /** + * The data for the table. + * @default [] + * @type TData[] + */ + data: TData[]; + + /** + * The columns of the table. + * @default [] + * @type ColumnDef[] + */ + columns: ColumnDef[]; + + /** + * The number of pages in the table. + * @type number + */ + pageCount: number; + + /** + * The default number of rows per page. + * @default 10 + * @type number | undefined + * @example 20 + */ + defaultPerPage?: number; + + /** + * The default sort order. + * @default undefined + * @type `${Extract}.${"asc" | "desc"}` | undefined + * @example "createdAt.desc" + */ + defaultSort?: `${Extract}.${'asc' | 'desc'}`; + + /** + * Defines filter fields for the table. Supports both dynamic faceted filters and search filters. + * - Faceted filters are rendered when `options` are provided for a filter field. + * - Otherwise, search filters are rendered. + * + * The indie filter field `value` represents the corresponding column name in the database table. + * @default [] + * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: React.ComponentType<{ className?: string }> }[] }[] + * @example + * ```ts + * // Render a search filter + * const filterFields = [ + * { label: "Title", value: "title", placeholder: "Search titles" } + * ]; + * // Render a faceted filter + * const filterFields = [ + * { + * label: "Status", + * value: "status", + * options: [ + * { label: "Todo", value: "todo" }, + * { label: "In Progress", value: "in-progress" }, + * { label: "Done", value: "done" }, + * { label: "Canceled", value: "canceled" } + * ] + * } + * ]; + * ``` + */ + filterFields?: DataTableFilterField[]; + + /** + * Enable notion like column filters. + * Advanced filters and column filters cannot be used at the same time. + * @default false + * @type boolean + */ + enableAdvancedFilter?: boolean; } const schema = z.object({ - page: z.coerce.number().default(1), - per_page: z.coerce.number().optional(), - sort: z.string().optional(), -}) + page: z.coerce.number().default(1), + per_page: z.coerce.number().optional(), + sort: z.string().optional(), +}); export function useDataTable({ - data, - columns, - pageCount, - defaultPerPage = 10, - defaultSort, - filterFields = [], - enableAdvancedFilter = false, + data, + columns, + pageCount, + defaultPerPage = 10, + defaultSort, + filterFields = [], + enableAdvancedFilter = false, }: UseDataTableProps) { - const location = useLocation(); - const pathname = location.pathname; -const [searchParams, setSearchParams] = useSearchParams(); - // Search params - const search = schema.parse(Object.fromEntries(searchParams)) - const page = search.page - const perPage = search.per_page ?? defaultPerPage - const sort = search.sort ?? defaultSort - const [column, order] = sort?.split(".") ?? [] - - // Memoize computation of searchableColumns and filterableColumns - const { searchableColumns, filterableColumns } = React.useMemo(() => { - return { - searchableColumns: filterFields.filter((field) => !field.options), - filterableColumns: filterFields.filter((field) => field.options), - } - }, [filterFields]) - - // Create query string - const createQueryString = React.useCallback( - (params: Record) => { - const newSearchParams = new URLSearchParams(searchParams?.toString()) - - for (const [key, value] of Object.entries(params)) { - if (value === null) { - newSearchParams.delete(key) - } else { - newSearchParams.set(key, String(value)) - } - } - - return newSearchParams.toString() - }, - [searchParams] - ) - - // Initial column filters - const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { - return Array.from(searchParams.entries()).reduce( - (filters, [key, value]) => { - const filterableColumn = filterableColumns.find( - (column) => column.value === key - ) - const searchableColumn = searchableColumns.find( - (column) => column.value === key + const location = useLocation(); + const pathname = location.pathname; + const [searchParams, setSearchParams] = useSearchParams(); + // Search params + const search = schema.parse(Object.fromEntries(searchParams)); + const page = search.page; + const perPage = search.per_page ?? defaultPerPage; + const sort = search.sort ?? defaultSort; + const [column, order] = sort?.split('.') ?? []; + + // Memoize computation of searchableColumns and filterableColumns + const { searchableColumns, filterableColumns } = React.useMemo(() => { + return { + searchableColumns: filterFields.filter((field) => !field.options), + filterableColumns: filterFields.filter((field) => field.options), + }; + }, [filterFields]); + + // Create query string + const createQueryString = React.useCallback( + (params: Record) => { + const newSearchParams = new URLSearchParams( + searchParams?.toString() + ); + + for (const [key, value] of Object.entries(params)) { + if (value === null) { + newSearchParams.delete(key); + } else { + newSearchParams.set(key, String(value)); + } + } + + return newSearchParams.toString(); + }, + [searchParams] + ); + + // Initial column filters + const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { + return Array.from(searchParams.entries()).reduce( + (filters, [key, value]) => { + const filterableColumn = filterableColumns.find( + (column) => column.value === key + ); + const searchableColumn = searchableColumns.find( + (column) => column.value === key + ); + + if (filterableColumn) { + filters.push({ + id: key, + value: value.split('.'), + }); + } else if (searchableColumn) { + filters.push({ + id: key, + value: [value], + }); + } + + return filters; + }, + [] + ); + }, [filterableColumns, searchableColumns, searchParams]); + + // Table states + const [rowSelection, setRowSelection] = React.useState({}); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [columnFilters, setColumnFilters] = + React.useState(initialColumnFilters); + + // Handle server-side pagination + const [{ pageIndex, pageSize }, setPagination] = + React.useState({ + pageIndex: page - 1, + pageSize: perPage, + }); + + const pagination = React.useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ); + + React.useEffect(() => { + redirect( + `${pathname}?${createQueryString({ + page: pageIndex + 1, + per_page: pageSize, + })}` + // { + // scroll: false, + // } + ); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pageIndex, pageSize]); + + // Handle server-side sorting + const [sorting, setSorting] = React.useState([ + { + id: column ?? '', + desc: order === 'desc', + }, + ]); + + React.useEffect(() => { + redirect( + `${pathname}?${createQueryString({ + page, + sort: sorting[0]?.id + ? `${sorting[0]?.id}.${sorting[0]?.desc ? 'desc' : 'asc'}` + : null, + })}` + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sorting]); + + // Handle server-side filtering + const debouncedSearchableColumnFilters = JSON.parse( + useDebounce( + JSON.stringify( + columnFilters.filter((filter) => { + return searchableColumns.find( + (column) => column.value === filter.id + ); + }) + ), + 500 ) + ) as ColumnFiltersState; - if (filterableColumn) { - filters.push({ - id: key, - value: value.split("."), - }) - } else if (searchableColumn) { - filters.push({ - id: key, - value: [value], - }) + const filterableColumnFilters = columnFilters.filter((filter) => { + return filterableColumns.find((column) => column.value === filter.id); + }); + + const [mounted, setMounted] = React.useState(false); + + React.useEffect(() => { + // Opt out when advanced filter is enabled, because it contains additional params + if (enableAdvancedFilter) return; + + // Prevent resetting the page on initial render + if (!mounted) { + setMounted(true); + return; } - return filters - }, - [] - ) - }, [filterableColumns, searchableColumns, searchParams]) - - // Table states - const [rowSelection, setRowSelection] = React.useState({}) - const [columnVisibility, setColumnVisibility] = - React.useState({}) - const [columnFilters, setColumnFilters] = - React.useState(initialColumnFilters) - - // Handle server-side pagination - const [{ pageIndex, pageSize }, setPagination] = - React.useState({ - pageIndex: page - 1, - pageSize: perPage, - }) - - const pagination = React.useMemo( - () => ({ - pageIndex, - pageSize, - }), - [pageIndex, pageSize] - ) - - React.useEffect(() => { - redirect( - `${pathname}?${createQueryString({ - page: pageIndex + 1, - per_page: pageSize, - })}`, - // { - // scroll: false, - // } - ) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageIndex, pageSize]) - - // Handle server-side sorting - const [sorting, setSorting] = React.useState([ - { - id: column ?? "", - desc: order === "desc", - }, - ]) - - React.useEffect(() => { - redirect( - `${pathname}?${createQueryString({ - page, - sort: sorting[0]?.id - ? `${sorting[0]?.id}.${sorting[0]?.desc ? "desc" : "asc"}` - : null, - })}` - ) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sorting]) - - // Handle server-side filtering - const debouncedSearchableColumnFilters = JSON.parse( - useDebounce( - JSON.stringify( - columnFilters.filter((filter) => { - return searchableColumns.find((column) => column.value === filter.id) - }) - ), - 500 - ) - ) as ColumnFiltersState - - const filterableColumnFilters = columnFilters.filter((filter) => { - return filterableColumns.find((column) => column.value === filter.id) - }) - - const [mounted, setMounted] = React.useState(false) - - React.useEffect(() => { - // Opt out when advanced filter is enabled, because it contains additional params - if (enableAdvancedFilter) return - - // Prevent resetting the page on initial render - if (!mounted) { - setMounted(true) - return - } - - // Initialize new params - const newParamsObject = { - page: 1, - } - - // Handle debounced searchable column filters - for (const column of debouncedSearchableColumnFilters) { - if (typeof column.value === "string") { - Object.assign(newParamsObject, { - [column.id]: typeof column.value === "string" ? column.value : null, - }) - } - } - - // Handle filterable column filters - for (const column of filterableColumnFilters) { - if (typeof column.value === "object" && Array.isArray(column.value)) { - Object.assign(newParamsObject, { [column.id]: column.value.join(".") }) - } - } - - // Remove deleted values - for (const key of searchParams.keys()) { - if ( - (searchableColumns.find((column) => column.value === key) && - !debouncedSearchableColumnFilters.find( - (column) => column.id === key - )) || - (filterableColumns.find((column) => column.value === key) && - !filterableColumnFilters.find((column) => column.id === key)) - ) { - Object.assign(newParamsObject, { [key]: null }) - } - } - - // After cumulating all the changes, push new params - redirect(`${pathname}?${createQueryString(newParamsObject)}`) - - table.setPageIndex(0) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify(debouncedSearchableColumnFilters), - // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify(filterableColumnFilters), - ]) - - const table = useReactTable({ - data, - columns, - pageCount: pageCount ?? -1, - state: { - pagination, - sorting, - columnVisibility, - rowSelection, - columnFilters, - }, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onPaginationChange: setPagination, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - manualPagination: true, - manualSorting: true, - manualFiltering: true, - }) - - return { table } -} \ No newline at end of file + // Initialize new params + const newParamsObject = { + page: 1, + }; + + // Handle debounced searchable column filters + for (const column of debouncedSearchableColumnFilters) { + if (typeof column.value === 'string') { + Object.assign(newParamsObject, { + [column.id]: + typeof column.value === 'string' ? column.value : null, + }); + } + } + + // Handle filterable column filters + for (const column of filterableColumnFilters) { + if ( + typeof column.value === 'object' && + Array.isArray(column.value) + ) { + Object.assign(newParamsObject, { + [column.id]: column.value.join('.'), + }); + } + } + + // Remove deleted values + for (const key of searchParams.keys()) { + if ( + (searchableColumns.find((column) => column.value === key) && + !debouncedSearchableColumnFilters.find( + (column) => column.id === key + )) || + (filterableColumns.find((column) => column.value === key) && + !filterableColumnFilters.find( + (column) => column.id === key + )) + ) { + Object.assign(newParamsObject, { [key]: null }); + } + } + + // After cumulating all the changes, push new params + redirect(`${pathname}?${createQueryString(newParamsObject)}`); + + table.setPageIndex(0); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(debouncedSearchableColumnFilters), + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(filterableColumnFilters), + ]); + + const table = useReactTable({ + data, + columns, + pageCount: pageCount ?? -1, + state: { + pagination, + sorting, + columnVisibility, + rowSelection, + columnFilters, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onPaginationChange: setPagination, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + manualPagination: true, + manualSorting: true, + manualFiltering: true, + }); + + return { table }; +} diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md new file mode 100644 index 0000000..c1e660d --- /dev/null +++ b/docs/DOCUMENTATION.md @@ -0,0 +1,755 @@ +# Cell Connect Documentation + +## 📖 Contents + +1. [Platform Overview](#platform-overview) +2. [Architecture](#architecture) +3. [Core Features](#core-features) +4. [User Flows](#user-flows) +5. [Data Models](#data-models) +6. [Access Control](#access-control) +7. [User Interface](#user-interface) +8. [Analytics & Reporting](#analytics--reporting) +9. [Technical Reference](#technical-reference) +10. [Support](#support) + +## Platform Overview + +### Purpose & Goals + +Cell Connect streamlines cell-based ministry through digital tools that: + +- Simplify administrative tasks +- Enhance member engagement +- Support leadership development +- Enable data-driven ministry decisions + +### Key Features + +```mermaid +mindmap + root((Cell Connect)) + [Core Operations] + Attendance + Member Records + Temp Leadership + [Resources] + Bible Study + Announcements + [Finance] + Giving Records + [Communication] + Prayer Requests + Leader Chat +``` + +### User Types + +| Role | Primary Functions | Access Level | +| ----------- | ---------------------------- | ------------------- | +| Admin | System management, reporting | Full access | +| Cell Leader | Cell operations, member care | Cell-scoped access | +| Temp Leader | Meeting facilitation | Time-limited access | +| Member | Personal engagement | Basic access | + +### Common Use Cases + +#### 1. Weekly Cell Operations + +```mermaid +sequenceDiagram + Leader->>System: Start Meeting + System->>Members: Notify + Members->>System: Mark Attendance + Members->>System: Record Giving + Leader->>System: End Meeting +``` + +#### 2. Leadership Transition + +```mermaid +flowchart LR + A[Assign Temp] --> B[Transfer Access] + B --> C[Lead Meeting] + C --> D[Restore Access] +``` + +#### 3. Member Journey + +```mermaid +stateDiagram-v2 + [*] --> Registration + Registration --> Active + Active --> Leadership + Active --> Inactive + Inactive --> Active +``` + +## Success Metrics + +### Core Metrics + +##### 1. Cell Health + +- **Weekly Attendance Rate** + + - Present/Total Members ratio + - Consistency score + - New visitor retention + +- **Member Engagement** + - Prayer request frequency + - Study guide interaction + - Communication activity + +```mermaid +graph LR + A[Cell Health] --> B[Attendance] + A --> C[Engagement] + A --> D[Growth] + + B --> E[Weekly Rate] + B --> F[Consistency] + + C --> G[Participation] + C --> H[Interaction] + + D --> I[New Members] + D --> J[Retention] +``` + +##### 2. Leadership Development + +- **Temporary Leadership** + + - Assignment frequency + - Performance rating + - Member participation rate + +- **Training Completion** + - Resource utilization + - Feedback scores + - Implementation success + +##### 3. Financial Health + +- **Giving Patterns** + - Transaction frequency + - Category distribution + - Growth trends + +```mermaid +pie title Transaction Distribution + "Regular Giving" : 50 + "Special Projects" : 30 + "Mercy Ministry" : 20 +``` + +##### 4. Platform Adoption + +- **Feature Usage** + + - Daily active users + - Feature engagement rates + - Mobile vs desktop usage + +- **System Health** + - Response time + - Error rates + - Sync success rate + +### Reporting Dashboard + +##### Weekly View + +```typescript +type WeeklyMetrics = { + attendance: { + rate: number; // 0-100% + consistency: number; // 0-100% + newVisitors: number; + }; + engagement: { + prayerRequests: number; + studyGuideViews: number; + messagesSent: number; + }; + giving: { + totalTransactions: number; + categorySplit: Record; + }; +}; +``` + +##### Monthly Trends + +```mermaid +graph TD + A[Monthly Report] --> B[Attendance Trend] + A --> C[Engagement Score] + A --> D[Leadership Stats] + A --> E[Financial Health] + + B --> F[Growth Rate] + C --> G[Activity Score] + D --> H[Development] + E --> I[Giving Patterns] +``` + +### Performance Targets + +| Metric | Target | Warning | Critical | +| ------------------- | ------- | --------- | -------- | +| Attendance Rate | >80% | 60-80% | <60% | +| Member Engagement | >70% | 50-70% | <50% | +| Leadership Rotation | Monthly | Quarterly | >Quarter | +| Platform Usage | >90% | 70-90% | <70% | + +## Architecture + +### High-Level Overview + +```mermaid +graph TD + subgraph Client + M[Mobile PWA] + W[Web App] + end + + subgraph API + GW[API Gateway] + A[Auth Service] + C[Core Service] + end + + subgraph Data + DB[(Supabase)] + CACHE[(Redis)] + end + + M --> GW + W --> GW + GW --> A + GW --> C + A --> DB + C --> DB + C --> CACHE +``` + +### Core Components + +##### Frontend Architecture + +```mermaid +graph LR + subgraph Frontend + N[Next.js] --> R[React] + R --> S[State/Tanstack] + R --> C[Components] + C --> P[Pages] + C --> F[Features] + end +``` + +##### Data Architecture + +```typescript +interface DataLayer { + // Real-time Subscriptions + attendance: RealtimeChannel; + prayers: RealtimeChannel; + announcements: RealtimeChannel; + + // Cached Data + studyGuides: CacheConfig; + memberProfiles: CacheConfig; + + // Offline Support + syncQueue: Queue; +} +``` + +### Security Model + +```mermaid +flowchart TD + A[Request] --> B{Auth} + B --> |Valid| C{Role Check} + C --> |Authorized| D[Resource] + B --> |Invalid| E[401] + C --> |Unauthorized| F[403] +``` + +### Data Flow + +##### Attendance Flow + +```mermaid +sequenceDiagram + participant Client + participant API + participant DB + participant Cache + + Client->>API: Mark Attendance + API->>DB: Store Record + API->>Cache: Update Stats + API->>Client: Confirm +``` + +### System Components + +##### 1. Frontend (Next.js) + +- PWA capabilities +- Offline first +- Real-time updates + +##### 2. API Layer + +- Supabase Functions +- Edge Computing +- Rate Limiting + +##### 3. Database (Supabase) + +- Row Level Security +- Real-time Subscriptions +- Automated Backups + +##### 4. Caching + +```typescript +interface CacheStrategy { + type: 'memory' | 'persistent'; + ttl: number; + invalidation: 'time' | 'event'; +} +``` + +### Integration Points + +```mermaid +graph TD + A[Cell Connect] --> B[SMS Gateway] + A --> C[Storage] + A --> D[Analytics] +``` + +### Performance Considerations + +| Component | SLA | Scaling Strategy | +| --------- | ------ | ---------------- | +| API | 99.9% | Edge Functions | +| Database | 99.99% | Automated | +| Cache | 99.9% | Memory-based | + +### Monitoring + +```typescript +interface Metrics { + performance: { + apiLatency: number; + cacheHitRate: number; + syncDelay: number; + }; + reliability: { + uptime: number; + errorRate: number; + syncFailures: number; + }; +} +``` + +## Core Features + +### 1. Attendance Management + +```mermaid +sequenceDiagram + Leader->>System: Start Meeting + System->>Members: Send Notifications + Members->>System: Mark Present/Absent + System->>Leader: Update Stats + Leader->>System: Close Meeting +``` + +- Simple present/absent marking +- Automated notifications +- Historical tracking + +### 2. Member Registration + +```typescript +type Member = { + id: string; + name: string; + phone: string; + status: 'active' | 'inactive'; + joinDate: Date; + cell: string; + leadership: 'member' | 'temp' | 'leader'; +}; +``` + +### 3. Temporary Leadership + +```mermaid +stateDiagram-v2 + [*] --> Regular + Regular --> TempAssigned + TempAssigned --> Active + Active --> Complete + Complete --> Regular +``` + +### 4. Bible Study Resources + +```typescript +type StudyGuide = { + id: string; + title: string; + date: Date; + content: string; + access: 'all' | 'leaders'; + attachments: string[]; +}; +``` + +### 5. Announcements + +```mermaid +flowchart LR + A[Create] --> B[Review] + B --> C[Publish] + C --> D[Notify] +``` + +### 6. Giving System + +```typescript +type Transaction = { + id: string; + memberId: string; + amount: number; + type: 'tithe' | 'offering' | 'special'; + date: Date; + method: 'cash' | 'mobile'; +}; +``` + +### 7. Prayer Requests + +```mermaid +graph TD + A[Submit] --> B{Private?} + B -->|Yes| C[Leaders Only] + B -->|No| D[Cell Visible] +``` + +### 8. Leader Chat + +```typescript +interface Chat { + directMessage: { + sender: string; + recipient: string; + content: string; + timestamp: Date; + }; + groupChat: { + cellId: string; + leadersOnly: boolean; + messages: Message[]; + }; +} +``` + +### Access Control Matrix + +| Feature | Admin | Leader | Temp | Member | +| ----------- | ------ | ------ | ------- | ------ | +| Attendance | Full | Manage | Record | Self | +| Members | Full | View | View | Self | +| Study Guide | Manage | Access | Access | View | +| Giving | Full | Record | Record | Self | +| Chat | Full | Full | Limited | Basic | + +### Data Flow + +```mermaid +graph TD + A[Input Layer] --> B[Validation] + B --> C[Storage] + C --> D[Notification] + D --> E[Analytics] +``` + +### Integration Points + +```typescript +interface Integrations { + sms: SMSGateway; + storage: FileStorage; + analytics: AnalyticsEngine; +} +``` + +## User Flows + +### Weekly Cell Meeting Flow + +```mermaid +sequenceDiagram + participant L as Leader + participant S as System + participant M as Members + + L->>S: Access Study Guide + L->>S: Start Meeting + S->>M: Send Notifications + M->>S: Mark Attendance + M->>S: Record Giving + M->>S: Submit Prayers + L->>S: End Meeting + S->>L: Generate Summary +``` + +### Temporary Leadership Flow + +```mermaid +stateDiagram-v2 + [*] --> RequestAssignment + RequestAssignment --> GrantAccess + GrantAccess --> LeadMeeting + LeadMeeting --> EndMeeting + EndMeeting --> RestoreAccess + RestoreAccess --> [*] +``` + +### Member Registration Flow + +```mermaid +graph TD + A[Start Registration] --> B[Basic Info] + B --> C[Verify Phone] + C --> D[Cell Assignment] + D --> E[Complete] +``` + +### Prayer Request Flow + +```mermaid +sequenceDiagram + Member->>System: Submit Prayer + System->>Member: Privacy Options + Member->>System: Set Visibility + System->>Leaders: Notify Leaders + opt Public Prayer + System->>Cell: Notify Cell + end +``` + +### Transaction Recording Flow + +```mermaid +graph LR + A[Input Amount] --> B[Select Type] + B --> C[Choose Method] + C --> D[Confirm] + D --> E[Receipt] +``` + +### Announcement Flow + +```mermaid +sequenceDiagram + Leader->>System: Create Announcement + System->>Leader: Preview + Leader->>System: Set Urgency + System->>Members: Notify + System->>Leader: Track Views +``` + +### Access Rights Progression + +```mermaid +graph TD + A[Member] --> B[Active Member] + B --> C[Temp Leader] + C --> D[Cell Leader] +``` + +### Error Flows + +```typescript +type ErrorFlow = { + type: 'validation' | 'network' | 'permission'; + retry: boolean; + fallback: string; + recovery: () => void; +}; +``` + +### Success States + +```typescript +type SuccessState = { + action: string; + confirmation: 'notification' | 'email' | 'sms'; + next: string[]; +}; +``` + +## Data Models + +### Core Entities + +```mermaid +erDiagram + CELL ||--o{ MEMBER : contains + CELL ||--o{ MEETING : has + MEMBER ||--o{ ATTENDANCE : marks + MEMBER ||--o{ TRANSACTION : makes + MEMBER ||--o{ PRAYER : submits + MEETING ||--o{ ATTENDANCE : records + MEETING ||--o{ TRANSACTION : collects +``` + +### Schema Definitions + +```typescript +interface Cell { + id: string; + name: string; + leaderId: string; + tempLeaderId?: string; + location: string; + meetingDay: string; + meetingTime: string; + status: 'active' | 'inactive'; +} + +interface Member { + id: string; + cellId: string; + name: string; + phone: string; + joinDate: Date; + status: 'active' | 'inactive'; + role: 'member' | 'temp' | 'leader'; + lastAttendance: Date; +} + +interface Meeting { + id: string; + cellId: string; + date: Date; + leaderId: string; + studyGuideId: string; + status: 'scheduled' | 'active' | 'completed'; + attendance: number; + transactions: number; +} + +interface Attendance { + id: string; + meetingId: string; + memberId: string; + status: 'present' | 'absent'; + timestamp: Date; +} + +interface Transaction { + id: string; + meetingId: string; + memberId: string; + amount: number; + type: 'tithe' | 'offering' | 'special'; + method: 'cash' | 'mobile'; + timestamp: Date; +} + +interface Prayer { + id: string; + memberId: string; + content: string; + isPrivate: boolean; + status: 'active' | 'answered'; + timestamp: Date; +} + +interface StudyGuide { + id: string; + title: string; + content: string; + date: Date; + attachments: string[]; + access: 'all' | 'leaders'; +} + +interface Message { + id: string; + senderId: string; + recipientId?: string; + cellId?: string; + content: string; + type: 'direct' | 'group'; + timestamp: Date; +} +``` + +### Database Indexes + +```sql +-- Performance Indexes +CREATE INDEX idx_member_cell ON members(cell_id); +CREATE INDEX idx_attendance_meeting ON attendance(meeting_id); +CREATE INDEX idx_transaction_date ON transactions(timestamp); + +-- Search Indexes +CREATE INDEX idx_member_phone ON members(phone); +CREATE INDEX idx_prayer_content ON prayers USING GIN (content); +``` + +### Relations & Constraints + +```sql +-- Foreign Keys +ALTER TABLE members +ADD CONSTRAINT fk_cell +FOREIGN KEY (cell_id) +REFERENCES cells(id); + +-- Check Constraints +ALTER TABLE transactions +ADD CONSTRAINT valid_amount +CHECK (amount > 0); + +-- Unique Constraints +ALTER TABLE members +ADD CONSTRAINT unique_phone +UNIQUE (phone); +``` + +### Access Policies + +```sql +-- Row Level Security +ALTER TABLE cells ENABLE ROW LEVEL SECURITY; + +CREATE POLICY cell_access ON cells +FOR SELECT +USING ( + auth.uid() IN ( + SELECT member_id FROM cell_members + WHERE cell_id = cells.id + ) +); +``` + +## Support + +- Contact Information +- Common Issues +- Training Resources diff --git a/public/images/hierarchy.svg b/public/images/hierarchy.svg new file mode 100644 index 0000000..8207c02 --- /dev/null +++ b/public/images/hierarchy.svg @@ -0,0 +1,10 @@ + + + + + + + + Senior PastorZone LeadersCell LeadersAssistant LeadersCell MembersRegular Members