@visns-studio/visns-components 5.11.4 → 5.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1213 -11
- package/package.json +5 -5
- package/src/components/crm/AssociationManager.jsx +151 -33
- package/src/components/crm/Field.jsx +115 -60
- package/src/components/crm/columns/ColumnRenderers.jsx +19 -0
- package/src/components/crm/generic/GenericDetail.jsx +40 -0
- package/src/components/crm/generic/GenericEditableTable.jsx +3 -3
- package/src/components/crm/generic/GenericQuote.jsx +353 -1
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +102 -22
- package/src/components/crm/generic/styles/GenericQuote.module.scss +203 -0
- package/src/components/proposal/ProposalTemplatePreview.css +364 -0
- package/src/components/proposal/ProposalTemplatePreview.jsx +331 -0
- package/src/components/proposal/ProposalTemplateSectionManager.css +321 -0
- package/src/components/proposal/ProposalTemplateSectionManager.jsx +473 -0
- package/src/components/proposal/SectionEditor.css +508 -0
- package/src/components/proposal/SectionEditor.jsx +412 -0
- package/src/components/proposal/SectionTypeSelector.css +277 -0
- package/src/components/proposal/SectionTypeSelector.jsx +158 -0
- package/src/components/proposal/VariableInserter.css +392 -0
- package/src/components/proposal/VariableInserter.jsx +260 -0
- package/src/components/proposal/index.js +5 -0
- package/src/components/styles/global.css +179 -1
- package/src/index.js +13 -0
- package/src/utils/urlBuilder.js +40 -0
package/README.md
CHANGED
|
@@ -951,6 +951,766 @@ File upload component with drag and drop support.
|
|
|
951
951
|
/>
|
|
952
952
|
```
|
|
953
953
|
|
|
954
|
+
### Association Management Components
|
|
955
|
+
|
|
956
|
+
#### AssociationManager
|
|
957
|
+
|
|
958
|
+
A sophisticated component for managing entity relationships and associations within your application. The AssociationManager provides an intuitive interface for creating, viewing, and managing complex many-to-many relationships between different data entities.
|
|
959
|
+
|
|
960
|
+
```jsx
|
|
961
|
+
<AssociationManager
|
|
962
|
+
config={{
|
|
963
|
+
title: "Client Associations",
|
|
964
|
+
description: "Manage relationships between clients and projects",
|
|
965
|
+
primaryEntity: {
|
|
966
|
+
id: "clients",
|
|
967
|
+
label: "Clients",
|
|
968
|
+
displayKey: "name",
|
|
969
|
+
fetchUrl: "/api/clients"
|
|
970
|
+
},
|
|
971
|
+
secondaryEntity: {
|
|
972
|
+
id: "projects",
|
|
973
|
+
label: "Projects",
|
|
974
|
+
displayKey: "title",
|
|
975
|
+
fetchUrl: "/api/projects"
|
|
976
|
+
},
|
|
977
|
+
association: {
|
|
978
|
+
fetchUrl: "/api/client-project-associations",
|
|
979
|
+
createUrl: "/api/client-project-associations",
|
|
980
|
+
deleteUrl: "/api/client-project-associations",
|
|
981
|
+
pivotTable: "client_project"
|
|
982
|
+
},
|
|
983
|
+
permissions: {
|
|
984
|
+
canCreate: true,
|
|
985
|
+
canDelete: true,
|
|
986
|
+
canView: true
|
|
987
|
+
},
|
|
988
|
+
ui: {
|
|
989
|
+
searchPlaceholder: "Search clients or projects...",
|
|
990
|
+
emptyStateMessage: "No associations found",
|
|
991
|
+
confirmDeleteMessage: "Are you sure you want to remove this association?"
|
|
992
|
+
}
|
|
993
|
+
}}
|
|
994
|
+
userProfile={userProfile}
|
|
995
|
+
onAssociationChange={(data) => console.log('Association changed:', data)}
|
|
996
|
+
/>
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
**Key Features:**
|
|
1000
|
+
|
|
1001
|
+
1. **Bidirectional Relationship Management**:
|
|
1002
|
+
- Create associations from either entity perspective
|
|
1003
|
+
- Automatic relationship synchronization
|
|
1004
|
+
- Real-time updates across related views
|
|
1005
|
+
|
|
1006
|
+
2. **Advanced Search and Filtering**:
|
|
1007
|
+
- Multi-entity search capabilities
|
|
1008
|
+
- Filter by entity type, status, or custom attributes
|
|
1009
|
+
- Debounced search to optimize performance
|
|
1010
|
+
|
|
1011
|
+
3. **Bulk Operations**:
|
|
1012
|
+
- Select multiple entities for batch association
|
|
1013
|
+
- Bulk delete with confirmation dialogs
|
|
1014
|
+
- Mass import/export functionality
|
|
1015
|
+
|
|
1016
|
+
4. **Visual Association Mapping**:
|
|
1017
|
+
- Interactive relationship visualization
|
|
1018
|
+
- Drag-and-drop association creation
|
|
1019
|
+
- Color-coded entity types
|
|
1020
|
+
|
|
1021
|
+
5. **Permission-Based Access Control**:
|
|
1022
|
+
- Granular permissions for create, read, update, delete
|
|
1023
|
+
- Role-based access to specific entity types
|
|
1024
|
+
- Custom permission validation
|
|
1025
|
+
|
|
1026
|
+
**Configuration Options:**
|
|
1027
|
+
|
|
1028
|
+
```jsx
|
|
1029
|
+
const associationConfig = {
|
|
1030
|
+
// Required: Basic entity configuration
|
|
1031
|
+
primaryEntity: {
|
|
1032
|
+
id: "clients", // Unique identifier
|
|
1033
|
+
label: "Clients", // Display name
|
|
1034
|
+
displayKey: "name", // Field to show in lists
|
|
1035
|
+
fetchUrl: "/api/clients", // API endpoint
|
|
1036
|
+
searchFields: ["name", "email"], // Fields to search
|
|
1037
|
+
icon: "Users", // Lucide icon name
|
|
1038
|
+
color: "#3b82f6" // Brand color
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
secondaryEntity: {
|
|
1042
|
+
id: "projects",
|
|
1043
|
+
label: "Projects",
|
|
1044
|
+
displayKey: "title",
|
|
1045
|
+
fetchUrl: "/api/projects",
|
|
1046
|
+
searchFields: ["title", "description"],
|
|
1047
|
+
icon: "FolderOpen",
|
|
1048
|
+
color: "#10b981"
|
|
1049
|
+
},
|
|
1050
|
+
|
|
1051
|
+
// Association configuration
|
|
1052
|
+
association: {
|
|
1053
|
+
fetchUrl: "/api/associations", // Get existing associations
|
|
1054
|
+
createUrl: "/api/associations", // Create new associations
|
|
1055
|
+
updateUrl: "/api/associations", // Update associations (optional)
|
|
1056
|
+
deleteUrl: "/api/associations", // Delete associations
|
|
1057
|
+
pivotTable: "client_project", // Database table name
|
|
1058
|
+
|
|
1059
|
+
// Additional pivot data
|
|
1060
|
+
pivotFields: [
|
|
1061
|
+
{
|
|
1062
|
+
name: "role",
|
|
1063
|
+
label: "Role",
|
|
1064
|
+
type: "select",
|
|
1065
|
+
options: [
|
|
1066
|
+
{ value: "manager", label: "Manager" },
|
|
1067
|
+
{ value: "contributor", label: "Contributor" }
|
|
1068
|
+
]
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: "start_date",
|
|
1072
|
+
label: "Start Date",
|
|
1073
|
+
type: "date"
|
|
1074
|
+
}
|
|
1075
|
+
]
|
|
1076
|
+
},
|
|
1077
|
+
|
|
1078
|
+
// UI customization
|
|
1079
|
+
ui: {
|
|
1080
|
+
layout: "split", // "split", "tabs", "cards"
|
|
1081
|
+
theme: "default", // "default", "compact", "detailed"
|
|
1082
|
+
showEntityCounts: true, // Show association counts
|
|
1083
|
+
showLastModified: true, // Show modification timestamps
|
|
1084
|
+
enableDragDrop: true, // Enable drag-and-drop
|
|
1085
|
+
confirmActions: true, // Show confirmation dialogs
|
|
1086
|
+
|
|
1087
|
+
// Custom labels and messages
|
|
1088
|
+
labels: {
|
|
1089
|
+
createButton: "Add Association",
|
|
1090
|
+
deleteButton: "Remove",
|
|
1091
|
+
searchPlaceholder: "Search entities...",
|
|
1092
|
+
noResultsMessage: "No entities found",
|
|
1093
|
+
loadingMessage: "Loading associations..."
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
|
|
1097
|
+
// Validation rules
|
|
1098
|
+
validation: {
|
|
1099
|
+
preventDuplicates: true, // Prevent duplicate associations
|
|
1100
|
+
requirePivotData: false, // Require additional pivot fields
|
|
1101
|
+
customValidation: (primary, secondary, pivotData) => {
|
|
1102
|
+
// Custom validation logic
|
|
1103
|
+
return { valid: true, message: "" };
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1106
|
+
|
|
1107
|
+
// Performance optimization
|
|
1108
|
+
performance: {
|
|
1109
|
+
enableVirtualization: true, // For large datasets
|
|
1110
|
+
pageSize: 50, // Pagination size
|
|
1111
|
+
debounceMs: 300, // Search debounce
|
|
1112
|
+
cacheResults: true // Cache API responses
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
**API Integration:**
|
|
1118
|
+
|
|
1119
|
+
The AssociationManager expects specific API response formats:
|
|
1120
|
+
|
|
1121
|
+
```javascript
|
|
1122
|
+
// GET /api/clients (Primary Entity)
|
|
1123
|
+
{
|
|
1124
|
+
"data": [
|
|
1125
|
+
{
|
|
1126
|
+
"id": 1,
|
|
1127
|
+
"name": "Acme Corp",
|
|
1128
|
+
"email": "contact@acme.com",
|
|
1129
|
+
"created_at": "2023-01-01T00:00:00Z"
|
|
1130
|
+
}
|
|
1131
|
+
],
|
|
1132
|
+
"total": 150,
|
|
1133
|
+
"page": 1,
|
|
1134
|
+
"per_page": 50
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// GET /api/associations (Existing Associations)
|
|
1138
|
+
{
|
|
1139
|
+
"data": [
|
|
1140
|
+
{
|
|
1141
|
+
"id": 1,
|
|
1142
|
+
"primary_id": 1,
|
|
1143
|
+
"secondary_id": 5,
|
|
1144
|
+
"pivot_data": {
|
|
1145
|
+
"role": "manager",
|
|
1146
|
+
"start_date": "2023-01-01"
|
|
1147
|
+
},
|
|
1148
|
+
"primary": {
|
|
1149
|
+
"id": 1,
|
|
1150
|
+
"name": "Acme Corp"
|
|
1151
|
+
},
|
|
1152
|
+
"secondary": {
|
|
1153
|
+
"id": 5,
|
|
1154
|
+
"title": "Website Redesign"
|
|
1155
|
+
},
|
|
1156
|
+
"created_at": "2023-01-01T00:00:00Z"
|
|
1157
|
+
}
|
|
1158
|
+
]
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// POST /api/associations (Create Association)
|
|
1162
|
+
{
|
|
1163
|
+
"primary_id": 1,
|
|
1164
|
+
"secondary_id": 5,
|
|
1165
|
+
"pivot_data": {
|
|
1166
|
+
"role": "manager",
|
|
1167
|
+
"start_date": "2023-01-01"
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Response:
|
|
1172
|
+
{
|
|
1173
|
+
"success": true,
|
|
1174
|
+
"data": {
|
|
1175
|
+
"id": 1,
|
|
1176
|
+
"primary_id": 1,
|
|
1177
|
+
"secondary_id": 5,
|
|
1178
|
+
"created_at": "2023-01-01T00:00:00Z"
|
|
1179
|
+
},
|
|
1180
|
+
"message": "Association created successfully"
|
|
1181
|
+
}
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
**Event Handling:**
|
|
1185
|
+
|
|
1186
|
+
```jsx
|
|
1187
|
+
<AssociationManager
|
|
1188
|
+
config={associationConfig}
|
|
1189
|
+
onAssociationChange={(event) => {
|
|
1190
|
+
// event.type: 'created', 'updated', 'deleted'
|
|
1191
|
+
// event.data: Association data
|
|
1192
|
+
// event.entities: Affected entities
|
|
1193
|
+
console.log('Association event:', event);
|
|
1194
|
+
}}
|
|
1195
|
+
onEntitySelect={(entity, type) => {
|
|
1196
|
+
// Handle entity selection
|
|
1197
|
+
console.log('Entity selected:', entity, type);
|
|
1198
|
+
}}
|
|
1199
|
+
onError={(error) => {
|
|
1200
|
+
// Handle errors
|
|
1201
|
+
console.error('Association error:', error);
|
|
1202
|
+
}}
|
|
1203
|
+
/>
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
**Advanced Usage Examples:**
|
|
1207
|
+
|
|
1208
|
+
1. **Client-Project Association with Roles**:
|
|
1209
|
+
```jsx
|
|
1210
|
+
// Managing client assignments to projects with specific roles
|
|
1211
|
+
<AssociationManager
|
|
1212
|
+
config={{
|
|
1213
|
+
primaryEntity: { id: "clients", label: "Clients", ... },
|
|
1214
|
+
secondaryEntity: { id: "projects", label: "Projects", ... },
|
|
1215
|
+
association: {
|
|
1216
|
+
pivotFields: [
|
|
1217
|
+
{
|
|
1218
|
+
name: "role",
|
|
1219
|
+
label: "Role in Project",
|
|
1220
|
+
type: "select",
|
|
1221
|
+
required: true,
|
|
1222
|
+
options: [
|
|
1223
|
+
{ value: "sponsor", label: "Project Sponsor" },
|
|
1224
|
+
{ value: "stakeholder", label: "Key Stakeholder" },
|
|
1225
|
+
{ value: "user", label: "End User" }
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1228
|
+
]
|
|
1229
|
+
}
|
|
1230
|
+
}}
|
|
1231
|
+
/>
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
2. **User-Permission Association**:
|
|
1235
|
+
```jsx
|
|
1236
|
+
// Managing user permissions with expiration dates
|
|
1237
|
+
<AssociationManager
|
|
1238
|
+
config={{
|
|
1239
|
+
primaryEntity: { id: "users", label: "Users", ... },
|
|
1240
|
+
secondaryEntity: { id: "permissions", label: "Permissions", ... },
|
|
1241
|
+
association: {
|
|
1242
|
+
pivotFields: [
|
|
1243
|
+
{
|
|
1244
|
+
name: "expires_at",
|
|
1245
|
+
label: "Expiration Date",
|
|
1246
|
+
type: "datetime",
|
|
1247
|
+
required: false
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
name: "granted_by",
|
|
1251
|
+
label: "Granted By",
|
|
1252
|
+
type: "select",
|
|
1253
|
+
fetchUrl: "/api/users/managers"
|
|
1254
|
+
}
|
|
1255
|
+
]
|
|
1256
|
+
}
|
|
1257
|
+
}}
|
|
1258
|
+
/>
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
### Business Card OCR Component
|
|
1262
|
+
|
|
1263
|
+
#### BusinessCardOcr (Enhanced v5.11.1+)
|
|
1264
|
+
|
|
1265
|
+
An advanced OCR (Optical Character Recognition) component specifically designed for scanning business cards and automatically extracting contact information. The component provides intelligent text recognition, client matching, and seamless contact management integration.
|
|
1266
|
+
|
|
1267
|
+
```jsx
|
|
1268
|
+
<BusinessCardOcr
|
|
1269
|
+
isOpen={showScanner}
|
|
1270
|
+
onClose={() => setShowScanner(false)}
|
|
1271
|
+
onContactSaved={(contact) => {
|
|
1272
|
+
console.log('Contact saved:', contact);
|
|
1273
|
+
// Handle the saved contact data
|
|
1274
|
+
}}
|
|
1275
|
+
fields={[
|
|
1276
|
+
{ id: 'name', label: 'Full Name', required: true },
|
|
1277
|
+
{ id: 'email', label: 'Email', type: 'email' },
|
|
1278
|
+
{ id: 'phone', label: 'Phone', type: 'tel' },
|
|
1279
|
+
{ id: 'company', label: 'Company' },
|
|
1280
|
+
{ id: 'position', label: 'Position' },
|
|
1281
|
+
{ id: 'address', label: 'Address', type: 'textarea' }
|
|
1282
|
+
]}
|
|
1283
|
+
fetchConfig={{
|
|
1284
|
+
ocrUrl: '/api/ocr/business-card',
|
|
1285
|
+
saveUrl: '/api/contacts',
|
|
1286
|
+
clientSearchUrl: '/api/clients/search',
|
|
1287
|
+
duplicateCheckUrl: '/api/contacts/check-duplicate'
|
|
1288
|
+
}}
|
|
1289
|
+
settings={{
|
|
1290
|
+
enableClientMatching: true,
|
|
1291
|
+
enableDuplicateCheck: true,
|
|
1292
|
+
autoSave: false,
|
|
1293
|
+
imageQuality: 0.8,
|
|
1294
|
+
supportedFormats: ['image/jpeg', 'image/png', 'image/webp']
|
|
1295
|
+
}}
|
|
1296
|
+
/>
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
**Key Features:**
|
|
1300
|
+
|
|
1301
|
+
1. **Advanced OCR Processing**:
|
|
1302
|
+
- High-accuracy text extraction from business card images
|
|
1303
|
+
- Intelligent field mapping and recognition
|
|
1304
|
+
- Support for multiple languages and formats
|
|
1305
|
+
- Automatic rotation and perspective correction
|
|
1306
|
+
|
|
1307
|
+
2. **Smart Client Matching**:
|
|
1308
|
+
- Automatic company name recognition
|
|
1309
|
+
- Fuzzy matching against existing client database
|
|
1310
|
+
- Confidence scoring for match suggestions
|
|
1311
|
+
- Manual override and confirmation options
|
|
1312
|
+
|
|
1313
|
+
3. **Duplicate Detection**:
|
|
1314
|
+
- Email and phone number duplicate checking
|
|
1315
|
+
- Visual comparison with existing contacts
|
|
1316
|
+
- Merge or create new contact options
|
|
1317
|
+
- Conflict resolution interface
|
|
1318
|
+
|
|
1319
|
+
4. **Interactive Editing Interface**:
|
|
1320
|
+
- Real-time field editing and validation
|
|
1321
|
+
- Drag-and-drop image upload
|
|
1322
|
+
- Live camera capture (mobile devices)
|
|
1323
|
+
- Field confidence indicators
|
|
1324
|
+
|
|
1325
|
+
5. **Contact Management Integration**:
|
|
1326
|
+
- Direct save to contact database
|
|
1327
|
+
- Custom field mapping and validation
|
|
1328
|
+
- Bulk import capabilities
|
|
1329
|
+
- Export to various formats
|
|
1330
|
+
|
|
1331
|
+
**Configuration Options:**
|
|
1332
|
+
|
|
1333
|
+
```jsx
|
|
1334
|
+
const businessCardConfig = {
|
|
1335
|
+
// Field definitions for extracted data
|
|
1336
|
+
fields: [
|
|
1337
|
+
{
|
|
1338
|
+
id: 'name',
|
|
1339
|
+
label: 'Full Name',
|
|
1340
|
+
type: 'text',
|
|
1341
|
+
required: true,
|
|
1342
|
+
validation: {
|
|
1343
|
+
minLength: 2,
|
|
1344
|
+
pattern: /^[a-zA-Z\s]+$/
|
|
1345
|
+
},
|
|
1346
|
+
ocrMapping: ['name', 'full_name', 'contact_name']
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
id: 'email',
|
|
1350
|
+
label: 'Email Address',
|
|
1351
|
+
type: 'email',
|
|
1352
|
+
required: false,
|
|
1353
|
+
validation: {
|
|
1354
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
1355
|
+
},
|
|
1356
|
+
ocrMapping: ['email', 'e_mail', 'email_address']
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
id: 'phone',
|
|
1360
|
+
label: 'Phone Number',
|
|
1361
|
+
type: 'tel',
|
|
1362
|
+
required: false,
|
|
1363
|
+
formatting: {
|
|
1364
|
+
mask: '(000) 000-0000',
|
|
1365
|
+
international: true
|
|
1366
|
+
},
|
|
1367
|
+
ocrMapping: ['phone', 'mobile', 'telephone', 'cell']
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
id: 'company',
|
|
1371
|
+
label: 'Company Name',
|
|
1372
|
+
type: 'text',
|
|
1373
|
+
required: false,
|
|
1374
|
+
clientMatching: true, // Enable client matching for this field
|
|
1375
|
+
ocrMapping: ['company', 'organization', 'business_name']
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
id: 'position',
|
|
1379
|
+
label: 'Job Title',
|
|
1380
|
+
type: 'text',
|
|
1381
|
+
required: false,
|
|
1382
|
+
ocrMapping: ['title', 'position', 'job_title', 'role']
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
id: 'address',
|
|
1386
|
+
label: 'Address',
|
|
1387
|
+
type: 'textarea',
|
|
1388
|
+
required: false,
|
|
1389
|
+
formatting: {
|
|
1390
|
+
multiline: true,
|
|
1391
|
+
rows: 3
|
|
1392
|
+
},
|
|
1393
|
+
ocrMapping: ['address', 'location', 'street_address']
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
id: 'website',
|
|
1397
|
+
label: 'Website',
|
|
1398
|
+
type: 'url',
|
|
1399
|
+
required: false,
|
|
1400
|
+
validation: {
|
|
1401
|
+
pattern: /^https?:\/\/.+/
|
|
1402
|
+
},
|
|
1403
|
+
ocrMapping: ['website', 'url', 'web', 'site']
|
|
1404
|
+
}
|
|
1405
|
+
],
|
|
1406
|
+
|
|
1407
|
+
// API endpoint configuration
|
|
1408
|
+
fetchConfig: {
|
|
1409
|
+
ocrUrl: '/api/ocr/business-card', // OCR processing endpoint
|
|
1410
|
+
saveUrl: '/api/contacts', // Contact save endpoint
|
|
1411
|
+
clientSearchUrl: '/api/clients/search', // Client matching endpoint
|
|
1412
|
+
duplicateCheckUrl: '/api/contacts/check', // Duplicate check endpoint
|
|
1413
|
+
uploadUrl: '/api/files/upload', // Image upload endpoint
|
|
1414
|
+
|
|
1415
|
+
// Request configuration
|
|
1416
|
+
timeout: 30000, // 30 second timeout
|
|
1417
|
+
retries: 3, // Retry failed requests
|
|
1418
|
+
headers: {
|
|
1419
|
+
'Accept': 'application/json',
|
|
1420
|
+
'X-Requested-With': 'XMLHttpRequest'
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
|
|
1424
|
+
// Component behavior settings
|
|
1425
|
+
settings: {
|
|
1426
|
+
// OCR settings
|
|
1427
|
+
imageQuality: 0.8, // Image compression quality
|
|
1428
|
+
maxImageSize: 5242880, // 5MB max file size
|
|
1429
|
+
supportedFormats: [ // Accepted image formats
|
|
1430
|
+
'image/jpeg',
|
|
1431
|
+
'image/png',
|
|
1432
|
+
'image/webp',
|
|
1433
|
+
'image/bmp'
|
|
1434
|
+
],
|
|
1435
|
+
|
|
1436
|
+
// Processing options
|
|
1437
|
+
enableClientMatching: true, // Enable company matching
|
|
1438
|
+
enableDuplicateCheck: true, // Check for duplicates
|
|
1439
|
+
autoSave: false, // Auto-save after OCR
|
|
1440
|
+
requireConfirmation: true, // Confirm before saving
|
|
1441
|
+
|
|
1442
|
+
// UI options
|
|
1443
|
+
showConfidenceScores: true, // Show OCR confidence
|
|
1444
|
+
enableManualEdit: true, // Allow manual editing
|
|
1445
|
+
showImagePreview: true, // Show uploaded image
|
|
1446
|
+
enableCameraCapture: true, // Enable camera on mobile
|
|
1447
|
+
|
|
1448
|
+
// Validation options
|
|
1449
|
+
validateOnBlur: true, // Validate fields on blur
|
|
1450
|
+
highlightErrors: true, // Highlight validation errors
|
|
1451
|
+
showTooltips: true, // Show field tooltips
|
|
1452
|
+
|
|
1453
|
+
// Performance options
|
|
1454
|
+
debounceMs: 500, // Debounce for auto-save
|
|
1455
|
+
cacheResults: true, // Cache OCR results
|
|
1456
|
+
enablePreprocessing: true // Image preprocessing
|
|
1457
|
+
},
|
|
1458
|
+
|
|
1459
|
+
// UI customization
|
|
1460
|
+
ui: {
|
|
1461
|
+
theme: 'default', // 'default', 'compact', 'minimal'
|
|
1462
|
+
layout: 'modal', // 'modal', 'inline', 'drawer'
|
|
1463
|
+
size: 'large', // 'small', 'medium', 'large'
|
|
1464
|
+
|
|
1465
|
+
// Labels and messages
|
|
1466
|
+
labels: {
|
|
1467
|
+
uploadButton: 'Upload Business Card',
|
|
1468
|
+
scanButton: 'Scan with Camera',
|
|
1469
|
+
saveButton: 'Save Contact',
|
|
1470
|
+
cancelButton: 'Cancel',
|
|
1471
|
+
editButton: 'Edit Information',
|
|
1472
|
+
processButton: 'Process Image'
|
|
1473
|
+
},
|
|
1474
|
+
|
|
1475
|
+
messages: {
|
|
1476
|
+
uploadPrompt: 'Drop an image here or click to upload',
|
|
1477
|
+
processing: 'Processing business card...',
|
|
1478
|
+
ocrSuccess: 'Text extracted successfully',
|
|
1479
|
+
ocrError: 'Failed to process image',
|
|
1480
|
+
saveSuccess: 'Contact saved successfully',
|
|
1481
|
+
duplicateFound: 'Duplicate contact detected'
|
|
1482
|
+
},
|
|
1483
|
+
|
|
1484
|
+
// Style customization
|
|
1485
|
+
styles: {
|
|
1486
|
+
primaryColor: '#3b82f6',
|
|
1487
|
+
successColor: '#10b981',
|
|
1488
|
+
errorColor: '#ef4444',
|
|
1489
|
+
borderRadius: '8px',
|
|
1490
|
+
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
```
|
|
1495
|
+
|
|
1496
|
+
**API Integration:**
|
|
1497
|
+
|
|
1498
|
+
The BusinessCardOcr component expects specific API response formats:
|
|
1499
|
+
|
|
1500
|
+
```javascript
|
|
1501
|
+
// POST /api/ocr/business-card (OCR Processing)
|
|
1502
|
+
// Request: FormData with image file
|
|
1503
|
+
{
|
|
1504
|
+
"image": File
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// Response:
|
|
1508
|
+
{
|
|
1509
|
+
"success": true,
|
|
1510
|
+
"data": {
|
|
1511
|
+
"extracted_text": "John Smith\nSenior Developer\nAcme Corp\nj.smith@acme.com\n(555) 123-4567",
|
|
1512
|
+
"fields": {
|
|
1513
|
+
"name": {
|
|
1514
|
+
"value": "John Smith",
|
|
1515
|
+
"confidence": 0.95,
|
|
1516
|
+
"position": { "x": 100, "y": 50, "width": 200, "height": 30 }
|
|
1517
|
+
},
|
|
1518
|
+
"email": {
|
|
1519
|
+
"value": "j.smith@acme.com",
|
|
1520
|
+
"confidence": 0.92,
|
|
1521
|
+
"position": { "x": 100, "y": 120, "width": 250, "height": 25 }
|
|
1522
|
+
},
|
|
1523
|
+
"company": {
|
|
1524
|
+
"value": "Acme Corp",
|
|
1525
|
+
"confidence": 0.88,
|
|
1526
|
+
"position": { "x": 100, "y": 80, "width": 150, "height": 25 }
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
"processing_time": 2.3,
|
|
1530
|
+
"image_url": "/storage/uploads/card_123.jpg"
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// GET /api/clients/search (Client Matching)
|
|
1535
|
+
{
|
|
1536
|
+
"query": "Acme Corp"
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Response:
|
|
1540
|
+
{
|
|
1541
|
+
"data": [
|
|
1542
|
+
{
|
|
1543
|
+
"id": 5,
|
|
1544
|
+
"name": "Acme Corporation",
|
|
1545
|
+
"match_score": 0.85,
|
|
1546
|
+
"address": "123 Business St",
|
|
1547
|
+
"phone": "(555) 987-6543",
|
|
1548
|
+
"website": "https://acme.com"
|
|
1549
|
+
}
|
|
1550
|
+
]
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// POST /api/contacts/check (Duplicate Check)
|
|
1554
|
+
{
|
|
1555
|
+
"email": "j.smith@acme.com",
|
|
1556
|
+
"phone": "(555) 123-4567"
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Response:
|
|
1560
|
+
{
|
|
1561
|
+
"duplicates": [
|
|
1562
|
+
{
|
|
1563
|
+
"id": 10,
|
|
1564
|
+
"name": "John Smith",
|
|
1565
|
+
"email": "j.smith@acme.com",
|
|
1566
|
+
"match_type": "email",
|
|
1567
|
+
"match_score": 1.0,
|
|
1568
|
+
"created_at": "2023-01-01T00:00:00Z"
|
|
1569
|
+
}
|
|
1570
|
+
]
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// POST /api/contacts (Save Contact)
|
|
1574
|
+
{
|
|
1575
|
+
"name": "John Smith",
|
|
1576
|
+
"email": "j.smith@acme.com",
|
|
1577
|
+
"phone": "(555) 123-4567",
|
|
1578
|
+
"company": "Acme Corp",
|
|
1579
|
+
"position": "Senior Developer",
|
|
1580
|
+
"client_id": 5,
|
|
1581
|
+
"source": "business_card_ocr",
|
|
1582
|
+
"confidence_scores": {
|
|
1583
|
+
"name": 0.95,
|
|
1584
|
+
"email": 0.92,
|
|
1585
|
+
"company": 0.88
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Response:
|
|
1590
|
+
{
|
|
1591
|
+
"success": true,
|
|
1592
|
+
"data": {
|
|
1593
|
+
"id": 15,
|
|
1594
|
+
"name": "John Smith",
|
|
1595
|
+
"created_at": "2023-01-01T00:00:00Z"
|
|
1596
|
+
},
|
|
1597
|
+
"message": "Contact created successfully"
|
|
1598
|
+
}
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
**Event Handling:**
|
|
1602
|
+
|
|
1603
|
+
```jsx
|
|
1604
|
+
<BusinessCardOcr
|
|
1605
|
+
isOpen={showScanner}
|
|
1606
|
+
onClose={() => setShowScanner(false)}
|
|
1607
|
+
onContactSaved={(contact) => {
|
|
1608
|
+
// Handle successful contact save
|
|
1609
|
+
console.log('New contact saved:', contact);
|
|
1610
|
+
|
|
1611
|
+
// Optional: Update parent component state
|
|
1612
|
+
setContacts(prev => [...prev, contact]);
|
|
1613
|
+
|
|
1614
|
+
// Optional: Show success notification
|
|
1615
|
+
toast.success(`Contact ${contact.name} saved successfully`);
|
|
1616
|
+
}}
|
|
1617
|
+
onOcrComplete={(extractedData) => {
|
|
1618
|
+
// Handle OCR completion
|
|
1619
|
+
console.log('OCR extraction completed:', extractedData);
|
|
1620
|
+
|
|
1621
|
+
// Optional: Custom processing of extracted data
|
|
1622
|
+
if (extractedData.confidence < 0.7) {
|
|
1623
|
+
toast.warning('Low confidence OCR results. Please review carefully.');
|
|
1624
|
+
}
|
|
1625
|
+
}}
|
|
1626
|
+
onClientMatch={(matches) => {
|
|
1627
|
+
// Handle client matching results
|
|
1628
|
+
console.log('Client matches found:', matches);
|
|
1629
|
+
|
|
1630
|
+
// Optional: Auto-select high confidence matches
|
|
1631
|
+
if (matches.length === 1 && matches[0].match_score > 0.9) {
|
|
1632
|
+
// Auto-select the best match
|
|
1633
|
+
return matches[0];
|
|
1634
|
+
}
|
|
1635
|
+
}}
|
|
1636
|
+
onDuplicateDetected={(duplicates) => {
|
|
1637
|
+
// Handle duplicate detection
|
|
1638
|
+
console.log('Duplicate contacts found:', duplicates);
|
|
1639
|
+
|
|
1640
|
+
// Optional: Custom duplicate handling logic
|
|
1641
|
+
return new Promise((resolve) => {
|
|
1642
|
+
// Show custom duplicate resolution UI
|
|
1643
|
+
showDuplicateDialog(duplicates, resolve);
|
|
1644
|
+
});
|
|
1645
|
+
}}
|
|
1646
|
+
onError={(error) => {
|
|
1647
|
+
// Handle errors
|
|
1648
|
+
console.error('OCR error:', error);
|
|
1649
|
+
|
|
1650
|
+
// Optional: Custom error handling
|
|
1651
|
+
if (error.type === 'network') {
|
|
1652
|
+
toast.error('Network error. Please check your connection.');
|
|
1653
|
+
} else if (error.type === 'validation') {
|
|
1654
|
+
toast.error('Please correct the highlighted fields.');
|
|
1655
|
+
}
|
|
1656
|
+
}}
|
|
1657
|
+
/>
|
|
1658
|
+
```
|
|
1659
|
+
|
|
1660
|
+
**Advanced Usage Examples:**
|
|
1661
|
+
|
|
1662
|
+
1. **Integration with Existing Contact Form**:
|
|
1663
|
+
```jsx
|
|
1664
|
+
const ContactForm = () => {
|
|
1665
|
+
const [showOcr, setShowOcr] = useState(false);
|
|
1666
|
+
const [formData, setFormData] = useState({});
|
|
1667
|
+
|
|
1668
|
+
return (
|
|
1669
|
+
<div>
|
|
1670
|
+
<Form data={formData} onChange={setFormData}>
|
|
1671
|
+
{/* Regular form fields */}
|
|
1672
|
+
</Form>
|
|
1673
|
+
|
|
1674
|
+
<button onClick={() => setShowOcr(true)}>
|
|
1675
|
+
Scan Business Card
|
|
1676
|
+
</button>
|
|
1677
|
+
|
|
1678
|
+
<BusinessCardOcr
|
|
1679
|
+
isOpen={showOcr}
|
|
1680
|
+
onClose={() => setShowOcr(false)}
|
|
1681
|
+
onContactSaved={(contact) => {
|
|
1682
|
+
// Pre-fill form with OCR data
|
|
1683
|
+
setFormData(contact);
|
|
1684
|
+
setShowOcr(false);
|
|
1685
|
+
}}
|
|
1686
|
+
/>
|
|
1687
|
+
</div>
|
|
1688
|
+
);
|
|
1689
|
+
};
|
|
1690
|
+
```
|
|
1691
|
+
|
|
1692
|
+
2. **Bulk Business Card Processing**:
|
|
1693
|
+
```jsx
|
|
1694
|
+
const BulkCardProcessor = () => {
|
|
1695
|
+
const [queue, setQueue] = useState([]);
|
|
1696
|
+
|
|
1697
|
+
return (
|
|
1698
|
+
<BusinessCardOcr
|
|
1699
|
+
settings={{
|
|
1700
|
+
autoSave: true,
|
|
1701
|
+
enableBulkMode: true
|
|
1702
|
+
}}
|
|
1703
|
+
onContactSaved={(contact) => {
|
|
1704
|
+
setQueue(prev => [...prev, contact]);
|
|
1705
|
+
}}
|
|
1706
|
+
onBulkComplete={(contacts) => {
|
|
1707
|
+
console.log(`Processed ${contacts.length} business cards`);
|
|
1708
|
+
}}
|
|
1709
|
+
/>
|
|
1710
|
+
);
|
|
1711
|
+
};
|
|
1712
|
+
```
|
|
1713
|
+
|
|
954
1714
|
### Utility Components
|
|
955
1715
|
|
|
956
1716
|
#### CustomFetch
|
|
@@ -1309,9 +2069,9 @@ Response:
|
|
|
1309
2069
|
|
|
1310
2070
|
### Report Builder Component
|
|
1311
2071
|
|
|
1312
|
-
#### GenericReport
|
|
2072
|
+
#### GenericReport (Enhanced v5.10.11+)
|
|
1313
2073
|
|
|
1314
|
-
A powerful report building component that
|
|
2074
|
+
A powerful and enhanced report building component that provides an intuitive wizard-based interface for creating custom reports. The component has been significantly improved with new features and better user experience.
|
|
1315
2075
|
|
|
1316
2076
|
```jsx
|
|
1317
2077
|
<GenericReport
|
|
@@ -1322,19 +2082,108 @@ A powerful report building component that allows users to create custom reports
|
|
|
1322
2082
|
executeUrl: '/ajax/reportBuilder/execute',
|
|
1323
2083
|
suggestedJoinsUrl: '/ajax/reportBuilder/getSuggestedJoins',
|
|
1324
2084
|
}}
|
|
2085
|
+
userProfile={userProfile}
|
|
2086
|
+
businessTemplates={businessTemplates} // Optional pre-built templates
|
|
1325
2087
|
/>
|
|
1326
2088
|
```
|
|
1327
2089
|
|
|
1328
|
-
|
|
2090
|
+
**Major Enhancements:**
|
|
2091
|
+
|
|
2092
|
+
- **5-Step Wizard Interface**: Guided workflow with visual progress indicators
|
|
2093
|
+
- **Business Templates**: Pre-built report templates for common business scenarios
|
|
2094
|
+
- **Calculated Fields**: Create custom formulas and calculations within reports
|
|
2095
|
+
- **Smart Column Selection**: Automatic suggestions based on table relationships
|
|
2096
|
+
- **Enhanced Filtering**: Advanced filter options with AND/OR logic and grouping
|
|
2097
|
+
- **Live Preview**: Real-time preview of report data as you build
|
|
2098
|
+
- **Intelligent Currency Formatting**: Automatic detection and formatting of monetary fields
|
|
2099
|
+
- **Expandable Field Summary**: View selected fields in a collapsible overview
|
|
2100
|
+
- **Template Management**: Save, load, and share report templates
|
|
2101
|
+
- **Multiple Export Formats**: Export to Excel, CSV, PDF with formatting options
|
|
2102
|
+
|
|
2103
|
+
**New Features in Recent Updates:**
|
|
2104
|
+
|
|
2105
|
+
1. **Calculated Fields (v5.10.12+)**:
|
|
2106
|
+
```jsx
|
|
2107
|
+
// Users can create custom calculated fields
|
|
2108
|
+
{
|
|
2109
|
+
id: 'profit_margin',
|
|
2110
|
+
displayName: 'Profit Margin %',
|
|
2111
|
+
formula: 'ROUND(((revenue - cost) / revenue) * 100, 2)',
|
|
2112
|
+
type: 'decimal'
|
|
2113
|
+
}
|
|
2114
|
+
```
|
|
2115
|
+
|
|
2116
|
+
2. **Business Templates (v5.11.0+)**:
|
|
2117
|
+
```jsx
|
|
2118
|
+
// Pre-built templates for common scenarios
|
|
2119
|
+
const businessTemplates = [
|
|
2120
|
+
{
|
|
2121
|
+
id: 'utilization_planning',
|
|
2122
|
+
name: 'Utilization Planning',
|
|
2123
|
+
description: 'Weekly resource allocation and laydown utilization',
|
|
2124
|
+
icon: 'Calendar',
|
|
2125
|
+
mainTable: 'leads',
|
|
2126
|
+
defaultColumns: [...],
|
|
2127
|
+
calculatedFields: [...]
|
|
2128
|
+
}
|
|
2129
|
+
];
|
|
2130
|
+
```
|
|
2131
|
+
|
|
2132
|
+
3. **Smart Currency Detection**:
|
|
2133
|
+
- Automatically formats fields containing 'value', 'price', 'cost', 'amount', etc.
|
|
2134
|
+
- Supports Australian Dollar (AUD) formatting by default
|
|
2135
|
+
- Configurable currency formatting options
|
|
2136
|
+
|
|
2137
|
+
4. **Enhanced UI/UX**:
|
|
2138
|
+
- Lucide React icons throughout for consistency
|
|
2139
|
+
- Larger calculated field display for better visibility
|
|
2140
|
+
- Expandable selected fields summary
|
|
2141
|
+
- Step-by-step guidance with tooltips and help text
|
|
2142
|
+
|
|
2143
|
+
**The 5-Step Wizard Process:**
|
|
2144
|
+
|
|
2145
|
+
1. **Select Data Source**: Choose main table and review business templates
|
|
2146
|
+
2. **Choose Columns**: Select specific data fields with drag-and-drop reordering
|
|
2147
|
+
3. **Add Relationships**: Connect related tables with automatic join suggestions
|
|
2148
|
+
4. **Apply Filters**: Set conditions and date ranges with advanced logic
|
|
2149
|
+
5. **Generate & Export**: Preview results and export in multiple formats
|
|
2150
|
+
|
|
2151
|
+
**API Integration:**
|
|
2152
|
+
|
|
2153
|
+
The component expects these endpoint responses:
|
|
2154
|
+
|
|
2155
|
+
```javascript
|
|
2156
|
+
// Tables endpoint
|
|
2157
|
+
{
|
|
2158
|
+
"tables": [
|
|
2159
|
+
{
|
|
2160
|
+
"id": "clients",
|
|
2161
|
+
"label": "Clients",
|
|
2162
|
+
"description": "Customer information"
|
|
2163
|
+
}
|
|
2164
|
+
]
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// Columns endpoint
|
|
2168
|
+
{
|
|
2169
|
+
"columns": [
|
|
2170
|
+
{
|
|
2171
|
+
"name": "id",
|
|
2172
|
+
"type": "integer",
|
|
2173
|
+
"nullable": false,
|
|
2174
|
+
"key": "primary"
|
|
2175
|
+
}
|
|
2176
|
+
]
|
|
2177
|
+
}
|
|
1329
2178
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
2179
|
+
// Execute endpoint
|
|
2180
|
+
{
|
|
2181
|
+
"success": true,
|
|
2182
|
+
"data": [...],
|
|
2183
|
+
"total": 150,
|
|
2184
|
+
"query": "SELECT ... FROM ..."
|
|
2185
|
+
}
|
|
2186
|
+
```
|
|
1338
2187
|
|
|
1339
2188
|
### Sketch Field Component
|
|
1340
2189
|
|
|
@@ -1501,6 +2350,359 @@ We welcome contributions! Please follow these steps:
|
|
|
1501
2350
|
|
|
1502
2351
|
[MIT License](LICENSE)
|
|
1503
2352
|
|
|
2353
|
+
### Quote Management Components
|
|
2354
|
+
|
|
2355
|
+
#### GenericQuote (Enhanced with Proposal System v5.12.0+)
|
|
2356
|
+
|
|
2357
|
+
The GenericQuote component provides comprehensive quote management with integrated proposal generation capabilities. It supports both traditional quote display and full proposal creation with cover pages, table of contents, and customizable sections.
|
|
2358
|
+
|
|
2359
|
+
```jsx
|
|
2360
|
+
<GenericQuote
|
|
2361
|
+
config={{
|
|
2362
|
+
// Basic quote configuration
|
|
2363
|
+
data: quoteData,
|
|
2364
|
+
currency: "AUD",
|
|
2365
|
+
companyLogo: "/path/to/logo.png",
|
|
2366
|
+
|
|
2367
|
+
// Standard features
|
|
2368
|
+
showLineItems: true,
|
|
2369
|
+
allowEditing: true,
|
|
2370
|
+
showNotes: true,
|
|
2371
|
+
|
|
2372
|
+
// Enhanced proposal features (v5.12.0+)
|
|
2373
|
+
proposalMode: {
|
|
2374
|
+
enabled: true,
|
|
2375
|
+
templateId: 1,
|
|
2376
|
+
brandingProfileId: 2,
|
|
2377
|
+
customSections: [
|
|
2378
|
+
{
|
|
2379
|
+
type: 'overview',
|
|
2380
|
+
title: 'Project Overview',
|
|
2381
|
+
content: '<h1>Executive Summary</h1><p>This proposal outlines...</p>'
|
|
2382
|
+
},
|
|
2383
|
+
{
|
|
2384
|
+
type: 'terms_conditions',
|
|
2385
|
+
title: 'Terms & Conditions',
|
|
2386
|
+
content: '<h3>Payment Terms</h3><p>Net 30 days...</p>'
|
|
2387
|
+
}
|
|
2388
|
+
]
|
|
2389
|
+
}
|
|
2390
|
+
}}
|
|
2391
|
+
userProfile={userProfile}
|
|
2392
|
+
onQuoteSave={(quoteData) => handleQuoteSave(quoteData)}
|
|
2393
|
+
onProposalGenerate={(proposalData) => handleProposalGeneration(proposalData)}
|
|
2394
|
+
/>
|
|
2395
|
+
```
|
|
2396
|
+
|
|
2397
|
+
**New Proposal Features:**
|
|
2398
|
+
|
|
2399
|
+
1. **Proposal Mode Toggle**:
|
|
2400
|
+
- Seamlessly switch between quote view and proposal generation mode
|
|
2401
|
+
- Maintains full backward compatibility with existing quote functionality
|
|
2402
|
+
- Context-aware interface that adapts based on proposal configuration
|
|
2403
|
+
|
|
2404
|
+
2. **Template Management Integration**:
|
|
2405
|
+
- Select from pre-configured proposal templates
|
|
2406
|
+
- Template preview with variable substitution
|
|
2407
|
+
- Custom section creation and ordering
|
|
2408
|
+
- Dynamic content population from quote data
|
|
2409
|
+
|
|
2410
|
+
3. **Branding Profile Support**:
|
|
2411
|
+
- Company logo and branding integration
|
|
2412
|
+
- Custom color schemes and styling
|
|
2413
|
+
- Professional cover page generation
|
|
2414
|
+
- Consistent brand presentation
|
|
2415
|
+
|
|
2416
|
+
4. **Advanced Section Management**:
|
|
2417
|
+
- **Cover Page**: Company branding with proposal title and customer details
|
|
2418
|
+
- **Table of Contents**: Auto-generated from all proposal sections
|
|
2419
|
+
- **Overview**: Executive summary with custom headers (H1, H2, H3) and content
|
|
2420
|
+
- **Quote Items**: Pricing table following original quote structure
|
|
2421
|
+
- **Terms & Conditions**: Static legal terms (rarely changes)
|
|
2422
|
+
- **Payment Terms**: Dynamic payment schedules and due dates
|
|
2423
|
+
- **Review Log**: Version history and change tracking
|
|
2424
|
+
- **Agreement & Signatures**: Static signature section for approvals
|
|
2425
|
+
|
|
2426
|
+
5. **Dynamic Content System**:
|
|
2427
|
+
- Variable replacement throughout all sections
|
|
2428
|
+
- Real-time preview of populated content
|
|
2429
|
+
- Support for both static and dynamic sections
|
|
2430
|
+
- Custom variable definitions per template
|
|
2431
|
+
|
|
2432
|
+
**Configuration Options:**
|
|
2433
|
+
|
|
2434
|
+
```jsx
|
|
2435
|
+
const proposalConfig = {
|
|
2436
|
+
// Enable proposal features
|
|
2437
|
+
enabled: true,
|
|
2438
|
+
|
|
2439
|
+
// Template selection
|
|
2440
|
+
templateId: 1, // Selected proposal template
|
|
2441
|
+
allowTemplateChange: true, // Allow template switching
|
|
2442
|
+
|
|
2443
|
+
// Branding configuration
|
|
2444
|
+
brandingProfileId: 2, // Selected branding profile
|
|
2445
|
+
allowBrandingChange: true, // Allow branding changes
|
|
2446
|
+
|
|
2447
|
+
// Section customization
|
|
2448
|
+
customSections: [
|
|
2449
|
+
{
|
|
2450
|
+
type: 'overview',
|
|
2451
|
+
title: 'Project Overview',
|
|
2452
|
+
content: '<h1>Executive Summary</h1><p>Custom content...</p>',
|
|
2453
|
+
variables: {
|
|
2454
|
+
'project_scope': 'Website development and maintenance',
|
|
2455
|
+
'timeline': '8-12 weeks'
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
],
|
|
2459
|
+
|
|
2460
|
+
// Generation settings
|
|
2461
|
+
autoGenerateTOC: true, // Auto-generate table of contents
|
|
2462
|
+
includePageNumbers: true, // Add page numbers to PDF
|
|
2463
|
+
watermark: false, // Add watermark to pages
|
|
2464
|
+
|
|
2465
|
+
// PDF export options
|
|
2466
|
+
paperSize: 'A4', // Paper size for PDF generation
|
|
2467
|
+
orientation: 'portrait', // Page orientation
|
|
2468
|
+
margins: '40px', // Page margins
|
|
2469
|
+
|
|
2470
|
+
// UI customization
|
|
2471
|
+
showPreview: true, // Show live preview panel
|
|
2472
|
+
previewMode: 'split', // 'split', 'modal', 'tabs'
|
|
2473
|
+
enableSectionReorder: true, // Allow section drag-and-drop
|
|
2474
|
+
showVariableHelper: true, // Show available variables
|
|
2475
|
+
};
|
|
2476
|
+
```
|
|
2477
|
+
|
|
2478
|
+
**Available Variables for Dynamic Content:**
|
|
2479
|
+
|
|
2480
|
+
The proposal system supports these built-in variables that are automatically populated from quote and customer data:
|
|
2481
|
+
|
|
2482
|
+
```javascript
|
|
2483
|
+
// Customer Information
|
|
2484
|
+
{{customer_name}} // Customer company name
|
|
2485
|
+
{{customer_address}} // Customer full address
|
|
2486
|
+
{{customer_contact}} // Primary contact name
|
|
2487
|
+
{{customer_email}} // Primary contact email
|
|
2488
|
+
{{customer_phone}} // Primary contact phone
|
|
2489
|
+
|
|
2490
|
+
// Quote Information
|
|
2491
|
+
{{quote_number}} // Quote/proposal number
|
|
2492
|
+
{{quote_date}} // Quote creation date
|
|
2493
|
+
{{quote_valid_until}} // Quote expiration date
|
|
2494
|
+
{{total_amount}} // Total quote amount (formatted)
|
|
2495
|
+
{{total_ex_gst}} // Total excluding GST
|
|
2496
|
+
{{total_gst}} // GST amount
|
|
2497
|
+
{{currency}} // Currency symbol/code
|
|
2498
|
+
|
|
2499
|
+
// Company Information
|
|
2500
|
+
{{company_name}} // Your company name
|
|
2501
|
+
{{company_address}} // Your company address
|
|
2502
|
+
{{company_phone}} // Your company phone
|
|
2503
|
+
{{company_email}} // Your company email
|
|
2504
|
+
{{company_website}} // Your company website
|
|
2505
|
+
{{company_abn}} // Australian Business Number
|
|
2506
|
+
|
|
2507
|
+
// Project Information
|
|
2508
|
+
{{project_manager}} // Assigned project manager
|
|
2509
|
+
{{project_timeline}} // Estimated timeline
|
|
2510
|
+
{{project_scope}} // Project scope description
|
|
2511
|
+
|
|
2512
|
+
// Date Variables
|
|
2513
|
+
{{current_date}} // Current date
|
|
2514
|
+
{{due_date}} // Payment due date
|
|
2515
|
+
{{start_date}} // Project start date
|
|
2516
|
+
|
|
2517
|
+
// Item-Specific Variables
|
|
2518
|
+
{{items_onceoff}} // One-time purchase items table
|
|
2519
|
+
{{items_monthly}} // Monthly subscription items table
|
|
2520
|
+
{{items_yearly}} // Yearly subscription items table
|
|
2521
|
+
{{line_items_total}} // Total number of line items
|
|
2522
|
+
```
|
|
2523
|
+
|
|
2524
|
+
**Integration with visns-packages Backend:**
|
|
2525
|
+
|
|
2526
|
+
The GenericQuote component seamlessly integrates with the visns-packages proposal system:
|
|
2527
|
+
|
|
2528
|
+
```jsx
|
|
2529
|
+
// Backend integration endpoints
|
|
2530
|
+
const proposalEndpoints = {
|
|
2531
|
+
// Template management
|
|
2532
|
+
templates: '/ajax/proposal-templates',
|
|
2533
|
+
templatePreview: '/ajax/proposal-templates/{id}/preview',
|
|
2534
|
+
|
|
2535
|
+
// Branding profiles
|
|
2536
|
+
brandingProfiles: '/ajax/branding-profiles',
|
|
2537
|
+
brandingCSS: '/ajax/branding-profiles/{id}/css',
|
|
2538
|
+
|
|
2539
|
+
// PDF generation
|
|
2540
|
+
generatePDF: '/ajax/pdf/generate-proposal',
|
|
2541
|
+
previewHTML: '/ajax/pdf/preview-proposal',
|
|
2542
|
+
|
|
2543
|
+
// Variable helpers
|
|
2544
|
+
availableVariables: '/ajax/proposal-templates/variables/available'
|
|
2545
|
+
};
|
|
2546
|
+
```
|
|
2547
|
+
|
|
2548
|
+
**Event Handling:**
|
|
2549
|
+
|
|
2550
|
+
```jsx
|
|
2551
|
+
<GenericQuote
|
|
2552
|
+
config={quoteConfig}
|
|
2553
|
+
// Quote events (existing)
|
|
2554
|
+
onQuoteSave={(data) => console.log('Quote saved:', data)}
|
|
2555
|
+
onLineItemChange={(items) => console.log('Line items updated:', items)}
|
|
2556
|
+
|
|
2557
|
+
// New proposal events
|
|
2558
|
+
onProposalGenerate={(proposalData) => {
|
|
2559
|
+
console.log('Proposal generated:', proposalData);
|
|
2560
|
+
// Handle PDF generation or preview
|
|
2561
|
+
}}
|
|
2562
|
+
onTemplateChange={(templateId) => {
|
|
2563
|
+
console.log('Template changed to:', templateId);
|
|
2564
|
+
// Update template-specific settings
|
|
2565
|
+
}}
|
|
2566
|
+
onBrandingChange={(brandingId) => {
|
|
2567
|
+
console.log('Branding changed to:', brandingId);
|
|
2568
|
+
// Update branding-specific styling
|
|
2569
|
+
}}
|
|
2570
|
+
onSectionUpdate={(sections) => {
|
|
2571
|
+
console.log('Sections updated:', sections);
|
|
2572
|
+
// Handle custom section changes
|
|
2573
|
+
}}
|
|
2574
|
+
onVariableChange={(variables) => {
|
|
2575
|
+
console.log('Variables updated:', variables);
|
|
2576
|
+
// Handle variable substitution updates
|
|
2577
|
+
}}
|
|
2578
|
+
/>
|
|
2579
|
+
```
|
|
2580
|
+
|
|
2581
|
+
**Backward Compatibility:**
|
|
2582
|
+
|
|
2583
|
+
All existing GenericQuote functionality remains unchanged:
|
|
2584
|
+
- Standard quote display and editing
|
|
2585
|
+
- Line item management
|
|
2586
|
+
- Notes and attachments
|
|
2587
|
+
- Export capabilities
|
|
2588
|
+
- Custom actions and workflows
|
|
2589
|
+
|
|
2590
|
+
Proposal features are **opt-in** and activated only when `proposalConfig.enabled: true` is set.
|
|
2591
|
+
|
|
2592
|
+
**Advanced Usage Example:**
|
|
2593
|
+
|
|
2594
|
+
```jsx
|
|
2595
|
+
import React, { useState } from 'react';
|
|
2596
|
+
import { GenericQuote } from '@visns-studio/visns-components';
|
|
2597
|
+
|
|
2598
|
+
const QuoteProposalManager = () => {
|
|
2599
|
+
const [quoteData, setQuoteData] = useState({
|
|
2600
|
+
id: 1,
|
|
2601
|
+
number: 'Q-2024-001',
|
|
2602
|
+
customer: {
|
|
2603
|
+
name: 'Acme Corporation',
|
|
2604
|
+
address: '123 Business St, Sydney NSW 2000',
|
|
2605
|
+
contact: 'John Smith',
|
|
2606
|
+
email: 'john@acme.com'
|
|
2607
|
+
},
|
|
2608
|
+
items: [
|
|
2609
|
+
{
|
|
2610
|
+
description: 'Website Development',
|
|
2611
|
+
quantity: 1,
|
|
2612
|
+
rate: 5000,
|
|
2613
|
+
total: 5000,
|
|
2614
|
+
type: 'onceoff'
|
|
2615
|
+
},
|
|
2616
|
+
{
|
|
2617
|
+
description: 'Monthly Hosting',
|
|
2618
|
+
quantity: 1,
|
|
2619
|
+
rate: 99,
|
|
2620
|
+
total: 99,
|
|
2621
|
+
type: 'monthly'
|
|
2622
|
+
}
|
|
2623
|
+
],
|
|
2624
|
+
total: 5099
|
|
2625
|
+
});
|
|
2626
|
+
|
|
2627
|
+
const [proposalMode, setProposalMode] = useState(false);
|
|
2628
|
+
|
|
2629
|
+
const proposalConfig = {
|
|
2630
|
+
enabled: true,
|
|
2631
|
+
templateId: 1,
|
|
2632
|
+
brandingProfileId: 1,
|
|
2633
|
+
customSections: [
|
|
2634
|
+
{
|
|
2635
|
+
type: 'overview',
|
|
2636
|
+
title: 'Project Overview',
|
|
2637
|
+
content: `
|
|
2638
|
+
<h1>Executive Summary</h1>
|
|
2639
|
+
<p>This proposal outlines our recommended solution for {{customer_name}}.</p>
|
|
2640
|
+
|
|
2641
|
+
<h2>Project Scope</h2>
|
|
2642
|
+
<p>We will develop a modern, responsive website that meets your business requirements.</p>
|
|
2643
|
+
|
|
2644
|
+
<h3>Key Deliverables</h3>
|
|
2645
|
+
<ul>
|
|
2646
|
+
<li>Custom website design and development</li>
|
|
2647
|
+
<li>Content management system</li>
|
|
2648
|
+
<li>Mobile-responsive design</li>
|
|
2649
|
+
<li>SEO optimization</li>
|
|
2650
|
+
</ul>
|
|
2651
|
+
`
|
|
2652
|
+
}
|
|
2653
|
+
],
|
|
2654
|
+
autoGenerateTOC: true,
|
|
2655
|
+
showPreview: true,
|
|
2656
|
+
previewMode: 'split'
|
|
2657
|
+
};
|
|
2658
|
+
|
|
2659
|
+
return (
|
|
2660
|
+
<div className="quote-proposal-manager">
|
|
2661
|
+
<div className="mode-toggle">
|
|
2662
|
+
<button
|
|
2663
|
+
onClick={() => setProposalMode(false)}
|
|
2664
|
+
className={!proposalMode ? 'active' : ''}
|
|
2665
|
+
>
|
|
2666
|
+
Quote View
|
|
2667
|
+
</button>
|
|
2668
|
+
<button
|
|
2669
|
+
onClick={() => setProposalMode(true)}
|
|
2670
|
+
className={proposalMode ? 'active' : ''}
|
|
2671
|
+
>
|
|
2672
|
+
Proposal Builder
|
|
2673
|
+
</button>
|
|
2674
|
+
</div>
|
|
2675
|
+
|
|
2676
|
+
<GenericQuote
|
|
2677
|
+
config={{
|
|
2678
|
+
data: quoteData,
|
|
2679
|
+
currency: "AUD",
|
|
2680
|
+
showLineItems: true,
|
|
2681
|
+
allowEditing: !proposalMode,
|
|
2682
|
+
|
|
2683
|
+
// Enable proposal features based on mode
|
|
2684
|
+
proposalMode: proposalMode ? proposalConfig : { enabled: false }
|
|
2685
|
+
}}
|
|
2686
|
+
userProfile={userProfile}
|
|
2687
|
+
onQuoteSave={(data) => {
|
|
2688
|
+
setQuoteData(data);
|
|
2689
|
+
console.log('Quote updated:', data);
|
|
2690
|
+
}}
|
|
2691
|
+
onProposalGenerate={(proposalData) => {
|
|
2692
|
+
// Generate PDF or handle proposal data
|
|
2693
|
+
console.log('Generating proposal PDF:', proposalData);
|
|
2694
|
+
|
|
2695
|
+
// Example: Download PDF
|
|
2696
|
+
window.open(`/ajax/pdf/generate-proposal?quote_id=${quoteData.id}`, '_blank');
|
|
2697
|
+
}}
|
|
2698
|
+
/>
|
|
2699
|
+
</div>
|
|
2700
|
+
);
|
|
2701
|
+
};
|
|
2702
|
+
|
|
2703
|
+
export default QuoteProposalManager;
|
|
2704
|
+
```
|
|
2705
|
+
|
|
1504
2706
|
## Modal Actions in Components
|
|
1505
2707
|
|
|
1506
2708
|
### Using Modal Actions in GenericQuote Component
|