diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a65274
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# Dependencies
+node_modules/
+
+# Build output
+dist/
+
+# Local env files
+.env
+.env.local
+.env.*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea/
+*.swp
+*.swo
+*~
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# Logs
+*.log
+npm-debug.log*
+
+# TypeScript cache
+*.tsbuildinfo
diff --git a/PLAN_SESSION_NOTE_EXPORT.md b/PLAN_SESSION_NOTE_EXPORT.md
new file mode 100644
index 0000000..65d947e
--- /dev/null
+++ b/PLAN_SESSION_NOTE_EXPORT.md
@@ -0,0 +1,301 @@
+# Implementation Plan: Parent Session Note Export Feature
+
+## Overview
+Add the ability to export professional session notes for parents in PDF and Word (.docx) formats. The document will follow the ABA session note template with company branding, session data, service documentation, and signature sections.
+
+## Company Information
+- **Company Name**: ABA Spot
+- **Address**: 816 Pennsylvania Ave, Saint Cloud, FL, 34769
+- **Email**: ABASpotFL@gmail.com
+
+---
+
+## Phase 1: Update Data Model
+
+### File: `src/db/database.ts`
+
+Add new fields to the `Session` interface:
+
+```typescript
+// New type for service codes
+export type ServiceType = '97155' | '97153' | '97156';
+
+export interface Session {
+ // ... existing fields ...
+
+ // New fields for parent session note
+ sessionFocus?: string; // Renamed from "Chief Complaint"
+ location?: string; // Session location
+ totalUnits?: number; // Billing units
+ serviceType?: ServiceType; // CPT code selection
+ parentParticipation?: boolean; // Did parent participate?
+ parentParticipationNotes?: string; // If no, why not?
+
+ // Service-specific documentation fields
+ protocolModification?: string; // 97155: Description of Modification & Client Responses
+ protocolDescription?: string; // 97153: Activities, Protocol Description & Client Responses
+ familyTrainingDescription?: string; // 97156: Family Training Description
+ generalNotes?: string; // General Notes, Environmental Changes, Recommendations
+}
+```
+
+### Database Migration
+- Increment database version from 2 to 3
+- Add migration to handle existing sessions (set new fields to undefined)
+
+---
+
+## Phase 2: Update Session Recording UI
+
+### File: `src/pages/SessionPage.tsx`
+
+Add new input sections to capture session note data:
+
+1. **Header Section** (after session timer):
+ - Session Focus (text input)
+ - Location (text input, can auto-fill from client profile)
+ - Total Units (number input)
+
+2. **Service Type Selection** (new section):
+ - Radio buttons or tabs for:
+ - 97155 - Behavior Treatment with Protocol Modification (BCBA/BCaBA)
+ - 97153 - Behavior Treatment by Protocol (Direct service/RBT)
+ - 97156 - Family Training (BCBA/BCaBA)
+ - Show all by default, user selects which applies
+
+3. **Service Documentation Sections** (based on selection):
+ - 97155: "Description of Modification & Client Responses" textarea
+ - 97153: "Activities, Protocol Description & Client Responses" textarea
+ - 97156: "Family Training Description" textarea
+
+4. **Parent Participation Section**:
+ - Toggle: "Did Parent(s)/Caregiver(s) participate?"
+ - If No: "Why not?" text input
+
+5. **General Notes Section**:
+ - "General Notes, Environmental Changes, Recommendations, etc." textarea
+ - This is separate from the existing session notes
+
+### State Updates
+- Add state variables for all new fields
+- Update `saveSession()` function to include new fields
+- Auto-save includes new fields
+
+---
+
+## Phase 3: Install Word Document Library
+
+### Package Installation
+```bash
+npm install docx file-saver
+npm install --save-dev @types/file-saver
+```
+
+The `docx` library allows creating Word documents programmatically with:
+- Headers, footers
+- Tables
+- Text formatting
+- Sections and paragraphs
+
+---
+
+## Phase 4: Create Export Functions
+
+### File: `src/utils/export.ts`
+
+Add two new export functions:
+
+### 4.1 `exportParentSessionNotePDF(session, client)`
+
+Creates a PDF matching the template layout:
+
+**Page 1:**
+```
++------------------------------------------+
+| ABA Spot |
+| 816 Pennsylvania Ave, Saint Cloud, FL |
+| ABASpotFL@gmail.com |
++------------------------------------------+
+| Name: [client] | Date: [date] | Therapist: [name] |
+| Start: [time] | End: [time] | Total Units: [#] |
+| Session Focus: [text] | Location: [text] |
++------------------------------------------+
+| Summary of Services (choose only 1): |
++------------------------------------------+
+| [ ] 97155 - Behavior Treatment with Protocol Modification |
+| Description of Modification & Client Responses: |
+| [text area content] |
++------------------------------------------+
+| [ ] 97153 - Behavior Treatment by Protocol |
+| Activities, Protocol Description & Client Responses: |
+| [text area content] |
++------------------------------------------+
+| Did Parent(s)/Caregiver(s) participate? [Yes/No] |
+| If No, why not? [text] |
++------------------------------------------+
+```
+
+**Page 2:**
+```
++------------------------------------------+
+| [ ] 97156 - Family Training |
+| Description: |
+| [text area content] |
++------------------------------------------+
+| General Notes, Environmental Changes, |
+| Recommendations, etc. |
+| [text area content] |
++------------------------------------------+
+| Behavior Data Summary Table |
+| Behavior | Type | Value |
+| ---------|------|------------------------ |
+| [data rows] |
++------------------------------------------+
+| Caregiver Signature: ________________ |
+| |
+| Provider Signature: _________________ |
++------------------------------------------+
+```
+
+### 4.2 `exportParentSessionNoteDocx(session, client)`
+
+Creates a Word document with the same layout as PDF:
+- Uses `docx` library
+- Includes company header
+- Tables for header info
+- Service sections with checkboxes
+- Behavior data table
+- Signature lines at bottom
+
+### Helper Functions
+- `createCompanyHeader()` - Returns header with company info
+- `createSessionInfoTable()` - Returns table with session metadata
+- `createServiceSection()` - Returns formatted service documentation
+- `createBehaviorDataTable()` - Returns behavior summary table
+- `createSignatureSection()` - Returns signature lines
+
+---
+
+## Phase 5: Update Export UI
+
+### File: `src/pages/SessionDetailPage.tsx`
+
+Update the export modal to include new options:
+
+```tsx
+
+
+
Data Export
+
+
+
+
+
+
Parent Session Note
+
+
+
+
+ {session.notes && (
+
+
Notes Only
+
+
+ )}
+
+```
+
+---
+
+## Phase 6: Update Session Detail View
+
+### File: `src/pages/SessionDetailPage.tsx`
+
+Add display sections for new fields:
+- Show service type and documentation
+- Show parent participation status
+- Show general notes separately from session notes
+
+---
+
+## File Changes Summary
+
+| File | Changes |
+|------|---------|
+| `src/db/database.ts` | Add ServiceType, new Session fields, bump version |
+| `src/pages/SessionPage.tsx` | Add UI for new fields, update save logic |
+| `src/utils/export.ts` | Add `exportParentSessionNotePDF()`, `exportParentSessionNoteDocx()` |
+| `src/pages/SessionDetailPage.tsx` | Update export modal, display new fields |
+| `package.json` | Add `docx` and `file-saver` dependencies |
+
+---
+
+## Document Layout Details
+
+### Header (both formats)
+- Company name: "ABA Spot" (bold, centered)
+- Address: "816 Pennsylvania Ave, Saint Cloud, FL, 34769"
+- Email: "ABASpotFL@gmail.com"
+
+### Session Info Table
+| Field | Value |
+|-------|-------|
+| Name | Client name |
+| Date | Session date |
+| Therapist | (from app user or manual entry) |
+| Start Time | Session start |
+| End Time | Session end |
+| Total Units | User input |
+| Session Focus | User input |
+| Location | User input or from client profile |
+
+### Service Sections
+- Checkbox indicator for selected service
+- Section header with CPT code and description
+- Text content area
+
+### Behavior Data Table
+| Behavior | Type | Category | Value |
+|----------|------|----------|-------|
+| Auto-populated from session data |
+
+### Signature Section
+```
+Caregiver Signature: _______________________________
+
+Provider Signature: ________________________________
+```
+
+---
+
+## Questions/Decisions
+
+1. **Therapist Name**: Should this come from:
+ - A new "therapist" field in settings?
+ - Manual entry per session?
+ - Default to empty for now?
+
+2. **Auto-fill Location**: Should location auto-fill from client profile if available?
+
+3. **Service Type Default**: Should all service types be shown, or only the selected one in the export?
+
+---
+
+## Implementation Order
+
+1. Update database schema and types
+2. Install dependencies (`docx`, `file-saver`)
+3. Update SessionPage.tsx with new input fields
+4. Create PDF export function
+5. Create Word export function
+6. Update SessionDetailPage.tsx export modal
+7. Test all exports
+8. Commit and push
+
+---
+
+## Estimated Complexity
+- Database changes: Low
+- UI changes: Medium (multiple new form fields)
+- Export functions: High (PDF/Word generation with tables)
+- Total new lines of code: ~500-700
diff --git a/package-lock.json b/package-lock.json
index d86a837..dc8a50f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,8 @@
"date-fns": "^4.1.0",
"dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0",
+ "docx": "^9.5.1",
+ "file-saver": "^2.0.5",
"jspdf": "^3.0.4",
"jspdf-autotable": "^5.0.2",
"react": "^19.2.0",
@@ -21,6 +23,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
+ "@types/file-saver": "^2.0.7",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
@@ -68,7 +71,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2908,6 +2910,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/file-saver": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
+ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2919,9 +2928,7 @@
"version": "24.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz",
"integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
- "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -2944,7 +2951,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3031,7 +3037,6 @@
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.50.0",
"@typescript-eslint/types": "8.50.0",
@@ -3283,7 +3288,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3536,7 +3540,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3775,6 +3778,12 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4082,8 +4091,7 @@
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz",
"integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==",
- "license": "Apache-2.0",
- "peer": true
+ "license": "Apache-2.0"
},
"node_modules/dexie-react-hooks": {
"version": "4.2.0",
@@ -4096,6 +4104,41 @@
"react": ">=16"
}
},
+ "node_modules/docx": {
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/docx/-/docx-9.5.1.tgz",
+ "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "^24.0.1",
+ "hash.js": "^1.1.7",
+ "jszip": "^3.10.1",
+ "nanoid": "^5.1.3",
+ "xml": "^1.0.1",
+ "xml-js": "^1.6.8"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/docx/node_modules/nanoid": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
+ "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
"node_modules/dompurify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
@@ -4375,7 +4418,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -4653,6 +4695,12 @@
"node": ">=16.0.0"
}
},
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+ "license": "MIT"
+ },
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -5097,6 +5145,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -5158,6 +5216,12 @@
"node": ">= 4"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
"node_modules/immer": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
@@ -5195,6 +5259,12 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -5807,7 +5877,6 @@
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
"fast-png": "^6.2.0",
@@ -5829,6 +5898,24 @@
"jspdf": "^2 || ^3"
}
},
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/jszip/node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -5863,6 +5950,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -5937,6 +6033,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "license": "ISC"
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -6281,6 +6383,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6316,7 +6424,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6326,7 +6433,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -6346,7 +6452,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -6413,6 +6518,33 @@
"react-dom": ">=18"
}
},
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/recharts": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz",
@@ -6447,8 +6579,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -6743,6 +6874,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -6824,6 +6964,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7020,6 +7166,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -7320,7 +7481,6 @@
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
"dev": true,
"license": "BSD-2-Clause",
- "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -7500,7 +7660,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -7556,7 +7715,6 @@
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
@@ -7687,6 +7845,12 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
@@ -7738,7 +7902,6 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -8130,7 +8293,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -8188,7 +8350,6 @@
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -8431,6 +8592,24 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/xml": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
+ "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
+ "license": "MIT"
+ },
+ "node_modules/xml-js": {
+ "version": "1.6.11",
+ "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
+ "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "xml-js": "bin/cli.js"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -8457,7 +8636,6 @@
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index a744c20..cd07f6b 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
"date-fns": "^4.1.0",
"dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0",
+ "docx": "^9.5.1",
+ "file-saver": "^2.0.5",
"jspdf": "^3.0.4",
"jspdf-autotable": "^5.0.2",
"react": "^19.2.0",
@@ -23,6 +25,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
+ "@types/file-saver": "^2.0.7",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
diff --git a/src/db/database.ts b/src/db/database.ts
index 30debd0..27ec6a3 100644
--- a/src/db/database.ts
+++ b/src/db/database.ts
@@ -2,6 +2,8 @@ import Dexie, { type EntityTable } from 'dexie';
export type DataType = 'frequency' | 'duration' | 'interval' | 'event' | 'deceleration';
export type BehaviorCategory = 'acquisition' | 'deceleration';
+export type ServiceType = '97155' | '97153' | '97156';
+export type LocationType = 'Home' | 'Clinic';
// Predefined ABC options
export const ANTECEDENT_OPTIONS = [
@@ -51,6 +53,7 @@ export interface Client {
phone?: string;
address?: string;
location?: string;
+ defaultSessionLocation?: LocationType;
targetBehaviors: TargetBehavior[];
createdAt: string;
updatedAt: string;
@@ -89,6 +92,17 @@ export interface Session {
notes: string;
createdAt: string;
updatedAt: string;
+ // Parent session note fields
+ sessionFocus?: string;
+ sessionLocation?: LocationType;
+ totalUnits?: number;
+ serviceType?: ServiceType;
+ parentParticipation?: boolean;
+ parentParticipationNotes?: string;
+ protocolModification?: string;
+ protocolDescription?: string;
+ familyTrainingDescription?: string;
+ generalNotes?: string;
}
const db = new Dexie('ABADataApp') as Dexie & {
@@ -110,4 +124,9 @@ db.version(2).stores({
});
});
+db.version(3).stores({
+ clients: 'id, name, createdAt, updatedAt',
+ sessions: 'id, clientId, startTime, createdAt'
+});
+
export { db };
diff --git a/src/pages/SessionDetailPage.tsx b/src/pages/SessionDetailPage.tsx
index 1006597..fdcef12 100644
--- a/src/pages/SessionDetailPage.tsx
+++ b/src/pages/SessionDetailPage.tsx
@@ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom'
import { useLiveQuery } from 'dexie-react-hooks'
import { db, type BehaviorData } from '../db/database'
import { formatDuration, formatDateTime } from '../utils/time'
-import { exportSessionToCSV, exportSessionToPDF, exportNotesToText } from '../utils/export'
+import { exportSessionToCSV, exportSessionToPDF, exportNotesToText, exportParentSessionNotePDF, exportParentSessionNoteDocx } from '../utils/export'
import ConfirmDialog from '../components/ConfirmDialog'
import Modal from '../components/Modal'
@@ -93,14 +93,92 @@ export default function SessionDetailPage() {
{formatDateTime(session.startTime)}
-
+
Duration:
{formatDuration(session.durationMs ?? 0)}
+ {session.sessionLocation && (
+
+ Location:
+ {session.sessionLocation}
+
+ )}
+ {session.totalUnits && (
+
+ Units:
+ {session.totalUnits}
+
+ )}
+ {session.sessionFocus && (
+
+ Session Focus:
+ {session.sessionFocus}
+
+ )}
+ {/* Session Note Details */}
+ {(session.serviceType || session.parentParticipation !== undefined || session.generalNotes) && (
+ <>
+ Session Note Details
+
+ {session.serviceType && (
+
+ Service Type:
+
+ {session.serviceType === '97155' && '97155 - Behavior Treatment with Protocol Modification'}
+ {session.serviceType === '97153' && '97153 - Behavior Treatment by Protocol'}
+ {session.serviceType === '97156' && '97156 - Family Training'}
+
+
+ )}
+
+ {session.serviceType === '97155' && session.protocolModification && (
+
+
Description of Modification & Client Responses:
+
{session.protocolModification}
+
+ )}
+
+ {session.serviceType === '97153' && session.protocolDescription && (
+
+
Activities, Protocol Description & Client Responses:
+
{session.protocolDescription}
+
+ )}
+
+ {session.serviceType === '97156' && session.familyTrainingDescription && (
+
+
Family Training Description:
+
{session.familyTrainingDescription}
+
+ )}
+
+ {session.parentParticipation !== undefined && (
+
+
Parent/Caregiver Participation:
+
{session.parentParticipation ? 'Yes' : 'No'}
+ {session.parentParticipation === false && session.parentParticipationNotes && (
+
+ Reason:
+ {session.parentParticipationNotes}
+
+ )}
+
+ )}
+
+ {session.generalNotes && (
+
+
General Notes, Environmental Changes, Recommendations:
+
{session.generalNotes}
+
+ )}
+
+ >
+ )}
+
Behavior Data
{session.behaviorData.map(bd => (
@@ -259,8 +337,29 @@ export default function SessionDetailPage() {
title="Export Session"
>
+
Parent Session Note
+
+
+
Data Export
+
{session.notes && (
+ {/* Session Note Fields Toggle */}
+
+
+ {/* Collapsible Session Note Fields */}
+ {showSessionNoteFields && (
+
+ {/* Basic Info Row */}
+
+
+
+ setSessionFocus(e.target.value)}
+ placeholder="Enter session focus..."
+ />
+
+
+
+
+
+
+
+
+
+
+ setTotalUnits(e.target.value ? Number(e.target.value) : '')}
+ placeholder="0"
+ min={0}
+ />
+
+
+
+ {/* Service Type Selection */}
+
+
+ {/* Service-specific documentation */}
+ {serviceType === '97155' && (
+
+
+
+ )}
+
+ {serviceType === '97153' && (
+
+
+
+ )}
+
+ {serviceType === '97156' && (
+
+
+
+ )}
+
+ {/* Parent Participation */}
+
+
+ {parentParticipation === false && (
+
+
+ setParentParticipationNotes(e.target.value)}
+ placeholder="Explain why parent/caregiver did not participate..."
+ />
+
+ )}
+
+ {/* General Notes */}
+
+
+
+
+ )}
+
{/* Acq/Decel Tabs */}
{(acqBehaviors.length > 0 || decelBehaviors.length > 0) && (
diff --git a/src/utils/export.ts b/src/utils/export.ts
index 8219a1f..730287b 100644
--- a/src/utils/export.ts
+++ b/src/utils/export.ts
@@ -1,5 +1,7 @@
import { jsPDF } from 'jspdf'
import 'jspdf-autotable'
+import { Document, Packer, Paragraph, Table, TableCell, TableRow, TextRun, WidthType, AlignmentType } from 'docx'
+import { saveAs } from 'file-saver'
import type { Session, Client, BehaviorData } from '../db/database'
import { formatDate, formatDuration, formatDateTime } from './time'
@@ -220,3 +222,468 @@ function downloadFile(content: string, filename: string, mimeType: string): void
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
+
+// Company information for parent session notes
+const COMPANY_INFO = {
+ name: 'ABA Spot',
+ address: '816 Pennsylvania Ave, Saint Cloud, FL, 34769',
+ email: 'ABASpotFL@gmail.com'
+}
+
+function formatTimeOnly12h(isoString: string): string {
+ const date = new Date(isoString)
+ return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: true })
+}
+
+export function exportParentSessionNotePDF(session: Session, client: Client): void {
+ const doc = new jsPDF()
+ const pageWidth = doc.internal.pageSize.getWidth()
+ let currentY = 15
+
+ // Company Header
+ doc.setFontSize(18)
+ doc.setFont('helvetica', 'bold')
+ doc.text(COMPANY_INFO.name, pageWidth / 2, currentY, { align: 'center' })
+ currentY += 7
+
+ doc.setFontSize(10)
+ doc.setFont('helvetica', 'normal')
+ doc.text(COMPANY_INFO.address, pageWidth / 2, currentY, { align: 'center' })
+ currentY += 5
+ doc.text(COMPANY_INFO.email, pageWidth / 2, currentY, { align: 'center' })
+ currentY += 10
+
+ // Draw line under header
+ doc.setLineWidth(0.5)
+ doc.line(14, currentY, pageWidth - 14, currentY)
+ currentY += 8
+
+ // Session Info Table
+ doc.autoTable({
+ startY: currentY,
+ head: [],
+ body: [
+ ['Name:', client.name, 'Date:', formatDate(session.startTime), 'Therapist:', ''],
+ ['Start Time:', formatTimeOnly12h(session.startTime), 'End Time:', session.endTime ? formatTimeOnly12h(session.endTime) : '', 'Total Units:', session.totalUnits?.toString() || ''],
+ ['Session Focus:', session.sessionFocus || '', 'Location:', session.sessionLocation || '', '', '']
+ ],
+ theme: 'grid',
+ styles: { fontSize: 9, cellPadding: 2 },
+ columnStyles: {
+ 0: { fontStyle: 'bold', cellWidth: 25 },
+ 1: { cellWidth: 40 },
+ 2: { fontStyle: 'bold', cellWidth: 25 },
+ 3: { cellWidth: 35 },
+ 4: { fontStyle: 'bold', cellWidth: 25 },
+ 5: { cellWidth: 30 }
+ }
+ })
+
+ currentY = (doc as unknown as { lastAutoTable: { finalY: number } }).lastAutoTable.finalY + 8
+
+ // Summary of Services section
+ doc.setFontSize(11)
+ doc.setFont('helvetica', 'bold')
+ doc.text('Summary of Services:', 14, currentY)
+ currentY += 6
+
+ // Service Type 97155
+ doc.setFontSize(10)
+ const is97155 = session.serviceType === '97155'
+ doc.setFont('helvetica', 'bold')
+ doc.text(`[${is97155 ? 'X' : ' '}] 97155 - Behavior Treatment with Protocol Modification (BCBA/BCaBA)`, 14, currentY)
+ currentY += 5
+
+ if (is97155 && session.protocolModification) {
+ doc.setFont('helvetica', 'italic')
+ doc.setFontSize(9)
+ doc.text('Description of Modification & Client Responses:', 18, currentY)
+ currentY += 4
+ doc.setFont('helvetica', 'normal')
+ const splitText = doc.splitTextToSize(session.protocolModification, pageWidth - 36)
+ doc.text(splitText, 18, currentY)
+ currentY += splitText.length * 4 + 4
+ } else {
+ currentY += 2
+ }
+
+ // Service Type 97153
+ doc.setFontSize(10)
+ const is97153 = session.serviceType === '97153'
+ doc.setFont('helvetica', 'bold')
+ doc.text(`[${is97153 ? 'X' : ' '}] 97153 - Behavior Treatment by Protocol (Direct service)`, 14, currentY)
+ currentY += 5
+
+ if (is97153 && session.protocolDescription) {
+ doc.setFont('helvetica', 'italic')
+ doc.setFontSize(9)
+ doc.text('Activities, Protocol Description & Client Responses:', 18, currentY)
+ currentY += 4
+ doc.setFont('helvetica', 'normal')
+ const splitText = doc.splitTextToSize(session.protocolDescription, pageWidth - 36)
+ doc.text(splitText, 18, currentY)
+ currentY += splitText.length * 4 + 4
+ } else {
+ currentY += 2
+ }
+
+ // Service Type 97156
+ doc.setFontSize(10)
+ const is97156 = session.serviceType === '97156'
+ doc.setFont('helvetica', 'bold')
+ doc.text(`[${is97156 ? 'X' : ' '}] 97156 - Family Training (BCBA/BCaBA)`, 14, currentY)
+ currentY += 5
+
+ if (is97156 && session.familyTrainingDescription) {
+ doc.setFont('helvetica', 'italic')
+ doc.setFontSize(9)
+ doc.text('Description:', 18, currentY)
+ currentY += 4
+ doc.setFont('helvetica', 'normal')
+ const splitText = doc.splitTextToSize(session.familyTrainingDescription, pageWidth - 36)
+ doc.text(splitText, 18, currentY)
+ currentY += splitText.length * 4 + 4
+ } else {
+ currentY += 2
+ }
+
+ // Parent Participation
+ currentY += 4
+ doc.setFontSize(10)
+ doc.setFont('helvetica', 'bold')
+ const participation = session.parentParticipation === true ? 'Yes' : session.parentParticipation === false ? 'No' : '__'
+ doc.text(`Did Parent(s)/Caregiver(s) participate? ${participation}`, 14, currentY)
+
+ if (session.parentParticipation === false && session.parentParticipationNotes) {
+ currentY += 5
+ doc.setFont('helvetica', 'normal')
+ doc.setFontSize(9)
+ doc.text(`If No, why not? ${session.parentParticipationNotes}`, 18, currentY)
+ }
+ currentY += 8
+
+ // Check if we need a new page
+ if (currentY > 200) {
+ doc.addPage()
+ currentY = 20
+ }
+
+ // Behavior Data Table
+ doc.setFontSize(11)
+ doc.setFont('helvetica', 'bold')
+ doc.text('Behavior Data Summary:', 14, currentY)
+ currentY += 4
+
+ const behaviorTableData = session.behaviorData.map(data => [
+ data.behaviorName,
+ data.dataType.charAt(0).toUpperCase() + data.dataType.slice(1),
+ data.category || 'acquisition',
+ getBehaviorValue(data)
+ ])
+
+ doc.autoTable({
+ startY: currentY,
+ head: [['Behavior', 'Type', 'Category', 'Value']],
+ body: behaviorTableData,
+ theme: 'striped',
+ headStyles: { fillColor: [25, 118, 210], fontSize: 9 },
+ styles: { fontSize: 9, cellPadding: 2 }
+ })
+
+ currentY = (doc as unknown as { lastAutoTable: { finalY: number } }).lastAutoTable.finalY + 8
+
+ // Check if we need a new page
+ if (currentY > 220) {
+ doc.addPage()
+ currentY = 20
+ }
+
+ // General Notes Section
+ doc.setFontSize(11)
+ doc.setFont('helvetica', 'bold')
+ doc.text('General Notes, Environmental Changes, Recommendations:', 14, currentY)
+ currentY += 5
+
+ doc.setFont('helvetica', 'normal')
+ doc.setFontSize(9)
+ if (session.generalNotes) {
+ const splitNotes = doc.splitTextToSize(session.generalNotes, pageWidth - 28)
+ doc.text(splitNotes, 14, currentY)
+ currentY += splitNotes.length * 4 + 4
+ } else {
+ doc.text('(No notes recorded)', 14, currentY)
+ currentY += 8
+ }
+
+ // Session Notes (original notes field)
+ if (session.notes) {
+ currentY += 4
+ doc.setFontSize(11)
+ doc.setFont('helvetica', 'bold')
+ doc.text('Additional Session Notes:', 14, currentY)
+ currentY += 5
+
+ doc.setFont('helvetica', 'normal')
+ doc.setFontSize(9)
+ const splitNotes = doc.splitTextToSize(session.notes, pageWidth - 28)
+ doc.text(splitNotes, 14, currentY)
+ currentY += splitNotes.length * 4 + 4
+ }
+
+ // Check if we need a new page for signatures
+ if (currentY > 240) {
+ doc.addPage()
+ currentY = 20
+ }
+
+ // Signature Section
+ currentY += 10
+ doc.setLineWidth(0.3)
+
+ doc.setFontSize(10)
+ doc.setFont('helvetica', 'normal')
+ doc.text('Caregiver Signature:', 14, currentY)
+ doc.line(55, currentY, 140, currentY)
+ currentY += 15
+
+ doc.text('Provider Signature:', 14, currentY)
+ doc.line(55, currentY, 140, currentY)
+
+ // Save the PDF
+ doc.save(`parent_session_note_${client.name}_${formatDate(session.startTime)}.pdf`)
+}
+
+export async function exportParentSessionNoteDocx(session: Session, client: Client): Promise {
+ const children: (Paragraph | Table)[] = []
+
+ // Company Header
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: COMPANY_INFO.name, bold: true, size: 36 })],
+ alignment: AlignmentType.CENTER,
+ spacing: { after: 100 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: COMPANY_INFO.address, size: 20 })],
+ alignment: AlignmentType.CENTER,
+ spacing: { after: 50 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: COMPANY_INFO.email, size: 20 })],
+ alignment: AlignmentType.CENTER,
+ spacing: { after: 200 }
+ })
+ )
+
+ // Session Info Table
+ const sessionInfoTable = new Table({
+ width: { size: 100, type: WidthType.PERCENTAGE },
+ rows: [
+ new TableRow({
+ children: [
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Name:', bold: true, size: 20 })] })], width: { size: 15, type: WidthType.PERCENTAGE } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: client.name, size: 20 })] })], width: { size: 20, type: WidthType.PERCENTAGE } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Date:', bold: true, size: 20 })] })], width: { size: 10, type: WidthType.PERCENTAGE } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: formatDate(session.startTime), size: 20 })] })], width: { size: 20, type: WidthType.PERCENTAGE } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Therapist:', bold: true, size: 20 })] })], width: { size: 15, type: WidthType.PERCENTAGE } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: '', size: 20 })] })], width: { size: 20, type: WidthType.PERCENTAGE } })
+ ]
+ }),
+ new TableRow({
+ children: [
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Start Time:', bold: true, size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: formatTimeOnly12h(session.startTime), size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'End Time:', bold: true, size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: session.endTime ? formatTimeOnly12h(session.endTime) : '', size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Total Units:', bold: true, size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: session.totalUnits?.toString() || '', size: 20 })] })] })
+ ]
+ }),
+ new TableRow({
+ children: [
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Session Focus:', bold: true, size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: session.sessionFocus || '', size: 20 })] })], columnSpan: 2 }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Location:', bold: true, size: 20 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: session.sessionLocation || '', size: 20 })] })], columnSpan: 2 })
+ ]
+ })
+ ]
+ })
+ children.push(sessionInfoTable)
+
+ // Summary of Services Header
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Summary of Services:', bold: true, size: 24 })],
+ spacing: { before: 300, after: 150 }
+ })
+ )
+
+ // Service Type 97155
+ const is97155 = session.serviceType === '97155'
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: `[${is97155 ? 'X' : ' '}] 97155 - Behavior Treatment with Protocol Modification (BCBA/BCaBA)`, bold: true, size: 20 })],
+ spacing: { after: 100 }
+ })
+ )
+ if (is97155 && session.protocolModification) {
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Description of Modification & Client Responses:', italics: true, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 50 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: session.protocolModification, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 150 }
+ })
+ )
+ }
+
+ // Service Type 97153
+ const is97153 = session.serviceType === '97153'
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: `[${is97153 ? 'X' : ' '}] 97153 - Behavior Treatment by Protocol (Direct service)`, bold: true, size: 20 })],
+ spacing: { after: 100 }
+ })
+ )
+ if (is97153 && session.protocolDescription) {
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Activities, Protocol Description & Client Responses:', italics: true, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 50 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: session.protocolDescription, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 150 }
+ })
+ )
+ }
+
+ // Service Type 97156
+ const is97156 = session.serviceType === '97156'
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: `[${is97156 ? 'X' : ' '}] 97156 - Family Training (BCBA/BCaBA)`, bold: true, size: 20 })],
+ spacing: { after: 100 }
+ })
+ )
+ if (is97156 && session.familyTrainingDescription) {
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Description:', italics: true, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 50 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: session.familyTrainingDescription, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 150 }
+ })
+ )
+ }
+
+ // Parent Participation
+ const participation = session.parentParticipation === true ? 'Yes' : session.parentParticipation === false ? 'No' : '____'
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: `Did Parent(s)/Caregiver(s) participate? ${participation}`, bold: true, size: 20 })],
+ spacing: { before: 200, after: 100 }
+ })
+ )
+ if (session.parentParticipation === false && session.parentParticipationNotes) {
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: `If No, why not? ${session.parentParticipationNotes}`, size: 18 })],
+ indent: { left: 360 },
+ spacing: { after: 150 }
+ })
+ )
+ }
+
+ // Behavior Data Table
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Behavior Data Summary:', bold: true, size: 24 })],
+ spacing: { before: 300, after: 150 }
+ })
+ )
+
+ const behaviorTableRows = [
+ new TableRow({
+ children: [
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Behavior', bold: true, size: 18 })] })], shading: { fill: '1976d2' } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Type', bold: true, size: 18 })] })], shading: { fill: '1976d2' } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Category', bold: true, size: 18 })] })], shading: { fill: '1976d2' } }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: 'Value', bold: true, size: 18 })] })], shading: { fill: '1976d2' } })
+ ]
+ }),
+ ...session.behaviorData.map(data => new TableRow({
+ children: [
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: data.behaviorName, size: 18 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: data.dataType.charAt(0).toUpperCase() + data.dataType.slice(1), size: 18 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: data.category || 'acquisition', size: 18 })] })] }),
+ new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: getBehaviorValue(data), size: 18 })] })] })
+ ]
+ }))
+ ]
+
+ children.push(new Table({
+ width: { size: 100, type: WidthType.PERCENTAGE },
+ rows: behaviorTableRows
+ }))
+
+ // General Notes Section
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'General Notes, Environmental Changes, Recommendations:', bold: true, size: 24 })],
+ spacing: { before: 300, after: 150 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: session.generalNotes || '(No notes recorded)', size: 18 })],
+ spacing: { after: 150 }
+ })
+ )
+
+ // Additional Session Notes
+ if (session.notes) {
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Additional Session Notes:', bold: true, size: 24 })],
+ spacing: { before: 200, after: 150 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: session.notes, size: 18 })],
+ spacing: { after: 150 }
+ })
+ )
+ }
+
+ // Signature Section
+ children.push(
+ new Paragraph({
+ children: [new TextRun({ text: 'Caregiver Signature: _______________________________', size: 20 })],
+ spacing: { before: 400, after: 300 }
+ }),
+ new Paragraph({
+ children: [new TextRun({ text: 'Provider Signature: _______________________________', size: 20 })],
+ spacing: { after: 200 }
+ })
+ )
+
+ // Create the document
+ const doc = new Document({
+ sections: [{
+ children: children
+ }]
+ })
+
+ // Generate and save the document
+ const blob = await Packer.toBlob(doc)
+ saveAs(blob, `parent_session_note_${client.name}_${formatDate(session.startTime)}.docx`)
+}