@squaredr/fieldcraft-pro 1.8.0 → 1.9.1

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.
@@ -378,6 +378,7 @@ var consultationBookingSchema = {
378
378
  required: true,
379
379
  config: {
380
380
  type: "appointment",
381
+ // Example slots — replace with dynamic data or a slots URL in production
381
382
  slots: [
382
383
  {
383
384
  date: "2026-09-01",
@@ -478,6 +479,55 @@ var consultationBookingSchema = {
478
479
  type: "divider",
479
480
  label: ""
480
481
  },
482
+ {
483
+ id: "hourly_rate",
484
+ type: "calculated",
485
+ label: "Hourly Rate",
486
+ config: {
487
+ type: "calculated",
488
+ expression: 'IF({service_type} = "legal", 150, IF({service_type} = "business", 200, IF({service_type} = "technical", 175, 0)))',
489
+ format: "currency",
490
+ decimalPlaces: 2,
491
+ prefix: "$"
492
+ }
493
+ },
494
+ {
495
+ id: "session_cost",
496
+ type: "calculated",
497
+ label: "Session Cost",
498
+ helpText: "Based on service type and session duration",
499
+ config: {
500
+ type: "calculated",
501
+ expression: "{hourly_rate} * {session_duration} / 60",
502
+ format: "currency",
503
+ decimalPlaces: 2,
504
+ prefix: "$"
505
+ }
506
+ },
507
+ {
508
+ id: "rush_fee",
509
+ type: "calculated",
510
+ label: "Rush Fee",
511
+ config: {
512
+ type: "calculated",
513
+ expression: 'IF({urgency} = "rush", 50, 0)',
514
+ format: "currency",
515
+ decimalPlaces: 2,
516
+ prefix: "$"
517
+ }
518
+ },
519
+ {
520
+ id: "total_cost",
521
+ type: "calculated",
522
+ label: "Total",
523
+ config: {
524
+ type: "calculated",
525
+ expression: "{session_cost} + {rush_fee}",
526
+ format: "currency",
527
+ decimalPlaces: 2,
528
+ prefix: "$"
529
+ }
530
+ },
481
531
  {
482
532
  id: "promo_code",
483
533
  type: "short_text",
@@ -497,10 +547,10 @@ var consultationBookingSchema = {
497
547
  provider: "stripe",
498
548
  publicKey: "",
499
549
  // Set your Stripe publishable key (pk_test_... or pk_live_...)
500
- amount: 2e4,
550
+ amountField: "total_cost",
501
551
  currency: "USD",
502
552
  description: "Consultation Session"
503
- // No serverUrl — developer must configure their own endpoint via customProps or serverUrl
553
+ // serverUrl: "" — developer must configure their own payment intent endpoint
504
554
  }
505
555
  }
506
556
  ]
@@ -997,13 +1047,50 @@ var ecommerceCheckoutSchema = {
997
1047
  prefix: "$"
998
1048
  }
999
1049
  },
1050
+ {
1051
+ id: "shipping_cost",
1052
+ type: "calculated",
1053
+ label: "Shipping",
1054
+ helpText: "Based on your selected shipping method",
1055
+ config: {
1056
+ type: "calculated",
1057
+ expression: 'IF({shipping_method} = "overnight", 29.99, IF({shipping_method} = "express", 14.99, IF({shipping_method} = "standard", 7.99, 0)))',
1058
+ format: "currency",
1059
+ decimalPlaces: 2,
1060
+ prefix: "$"
1061
+ }
1062
+ },
1063
+ {
1064
+ id: "insurance_cost",
1065
+ type: "calculated",
1066
+ label: "Shipping Insurance",
1067
+ config: {
1068
+ type: "calculated",
1069
+ expression: "IF({shipping_insurance} = true, 4.99, 0)",
1070
+ format: "currency",
1071
+ decimalPlaces: 2,
1072
+ prefix: "$"
1073
+ }
1074
+ },
1075
+ {
1076
+ id: "gift_wrap_cost",
1077
+ type: "calculated",
1078
+ label: "Gift Wrapping",
1079
+ config: {
1080
+ type: "calculated",
1081
+ expression: "IF({gift_wrap} = true, 5.99, 0)",
1082
+ format: "currency",
1083
+ decimalPlaces: 2,
1084
+ prefix: "$"
1085
+ }
1086
+ },
1000
1087
  {
1001
1088
  id: "order_total",
1002
1089
  type: "calculated",
1003
1090
  label: "Order Total",
1004
1091
  config: {
1005
1092
  type: "calculated",
1006
- expression: "{order_subtotal} + {order_tax}",
1093
+ expression: "{order_subtotal} + {order_tax} + {shipping_cost} + {insurance_cost} + {gift_wrap_cost}",
1007
1094
  format: "currency",
1008
1095
  decimalPlaces: 2,
1009
1096
  prefix: "$"
@@ -1025,10 +1112,10 @@ var ecommerceCheckoutSchema = {
1025
1112
  provider: "stripe",
1026
1113
  publicKey: "",
1027
1114
  // Set your Stripe publishable key (pk_test_... or pk_live_...)
1028
- amount: 1e4,
1115
+ amountField: "order_total",
1029
1116
  currency: "USD",
1030
1117
  description: "E-commerce Order"
1031
- // No serverUrl — developer must configure their own endpoint via customProps or serverUrl
1118
+ // serverUrl: "" — developer must configure their own payment intent endpoint
1032
1119
  }
1033
1120
  }
1034
1121
  ]
@@ -4,8 +4,24 @@ import { useState, useEffect, useMemo } from 'react';
4
4
  import { Input, Button, Separator, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@squaredr/fieldcraft-react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
7
+ // src/response-viewer/constants.ts
8
+ var DISPLAY_ONLY_TYPES = /* @__PURE__ */ new Set([
9
+ "info-block",
10
+ "info_block",
11
+ "section-header",
12
+ "page-break",
13
+ "image",
14
+ "divider",
15
+ "spacer",
16
+ "video",
17
+ "rich-text",
18
+ "welcome-screen",
19
+ "thank-you-screen",
20
+ "hidden",
21
+ "calculated"
22
+ ]);
7
23
  function formatDuration(ms) {
8
- if (ms == null) return "\u2014";
24
+ if (ms == null || ms < 0) return "\u2014";
9
25
  const totalSeconds = Math.round(ms / 1e3);
10
26
  if (totalSeconds < 60) return `${totalSeconds}s`;
11
27
  const minutes = Math.floor(totalSeconds / 60);
@@ -82,11 +98,7 @@ function ResponseTable({
82
98
  ) }),
83
99
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground whitespace-nowrap", children: new Date(response.submittedAt).toLocaleString() }),
84
100
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground whitespace-nowrap", children: formatDuration(response.completionTimeMs) }),
85
- /* @__PURE__ */ jsxs("td", { className: "px-3 py-2 text-foreground whitespace-nowrap", children: [
86
- answered,
87
- "/",
88
- totalFields
89
- ] }),
101
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground whitespace-nowrap", children: totalFields > 0 ? `${answered}/${totalFields}` : "\u2014" }),
90
102
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground whitespace-nowrap", children: response.totalScore ?? "\u2014" })
91
103
  ]
92
104
  },
@@ -104,21 +116,6 @@ function ResponseTable({
104
116
  ] })
105
117
  ] }) });
106
118
  }
107
- var DISPLAY_ONLY_TYPES = /* @__PURE__ */ new Set([
108
- "info-block",
109
- "info_block",
110
- "section-header",
111
- "page-break",
112
- "image",
113
- "divider",
114
- "spacer",
115
- "video",
116
- "rich-text",
117
- "welcome-screen",
118
- "thank-you-screen",
119
- "hidden",
120
- "calculated"
121
- ]);
122
119
 
123
120
  // src/response-viewer/clinical-display-data.ts
124
121
  var BODY_REGION_LABELS = {
@@ -739,7 +736,13 @@ function formatFallbackValue(value) {
739
736
  if (value == null) return "\u2014";
740
737
  if (typeof value === "boolean") return value ? "Yes" : "No";
741
738
  if (Array.isArray(value)) return value.map(formatFallbackValue).join(", ");
742
- if (typeof value === "object") return JSON.stringify(value, null, 2);
739
+ if (typeof value === "object") {
740
+ try {
741
+ return JSON.stringify(value, null, 2);
742
+ } catch {
743
+ return "[Object]";
744
+ }
745
+ }
743
746
  return String(value);
744
747
  }
745
748
  function TimelineView({ responses, questions, onSelect }) {
@@ -1253,26 +1256,11 @@ function exportToJson(responses, filename = "responses.json") {
1253
1256
  const content = JSON.stringify(responses, null, 2);
1254
1257
  downloadBlob(content, filename, "application/json;charset=utf-8;");
1255
1258
  }
1256
- var DISPLAY_ONLY_TYPES2 = /* @__PURE__ */ new Set([
1257
- "info-block",
1258
- "info_block",
1259
- "section-header",
1260
- "page-break",
1261
- "image",
1262
- "divider",
1263
- "spacer",
1264
- "video",
1265
- "rich-text",
1266
- "welcome-screen",
1267
- "thank-you-screen",
1268
- "hidden",
1269
- "calculated"
1270
- ]);
1271
1259
  function getExportableQuestions(schema) {
1272
1260
  const questions = [];
1273
1261
  for (const section of schema.sections) {
1274
1262
  for (const q of section.questions) {
1275
- if (DISPLAY_ONLY_TYPES2.has(q.type)) continue;
1263
+ if (DISPLAY_ONLY_TYPES.has(q.type)) continue;
1276
1264
  questions.push(q);
1277
1265
  }
1278
1266
  }
@@ -1356,7 +1344,7 @@ function downloadBlob(content, filename, mimeType) {
1356
1344
  document.body.appendChild(link);
1357
1345
  link.click();
1358
1346
  document.body.removeChild(link);
1359
- setTimeout(() => URL.revokeObjectURL(url), 1e4);
1347
+ setTimeout(() => URL.revokeObjectURL(url), 100);
1360
1348
  }
1361
1349
 
1362
1350
  // src/response-viewer/pagination-utils.ts
@@ -1476,14 +1464,15 @@ function applyFilters(responses, state) {
1476
1464
  function matchesDateRange(submittedAt, range) {
1477
1465
  if (!range.from && !range.to) return true;
1478
1466
  const submitted = new Date(submittedAt);
1467
+ if (isNaN(submitted.getTime())) return false;
1479
1468
  if (range.from) {
1480
- const from = new Date(range.from);
1481
- from.setHours(0, 0, 0, 0);
1469
+ const from = /* @__PURE__ */ new Date(range.from + "T00:00:00");
1470
+ if (isNaN(from.getTime())) return false;
1482
1471
  if (submitted < from) return false;
1483
1472
  }
1484
1473
  if (range.to) {
1485
- const to = new Date(range.to);
1486
- to.setHours(23, 59, 59, 999);
1474
+ const to = /* @__PURE__ */ new Date(range.to + "T23:59:59.999");
1475
+ if (isNaN(to.getTime())) return false;
1487
1476
  if (submitted > to) return false;
1488
1477
  }
1489
1478
  return true;
@@ -1594,7 +1583,7 @@ function ResponseViewerInner({
1594
1583
  [searchedResponses, filterState]
1595
1584
  );
1596
1585
  const pag = paginate(filteredResponses, currentPage, effectivePageSize);
1597
- const questions = getAllQuestions(schema);
1586
+ const questions = useMemo(() => getAllQuestions(schema), [schema]);
1598
1587
  const isSelectable = selectable && (!!onBulkDelete || !!onBulkExport);
1599
1588
  function handleSelect(response) {
1600
1589
  setSelectedResponse(response);
@@ -2043,7 +2032,7 @@ function ResponseViewerInner({
2043
2032
  response: selectedResponse,
2044
2033
  fields: getFields(selectedResponse),
2045
2034
  onBack: handleBack,
2046
- onDelete: onDelete && selectedResponse.sessionToken ? () => handleDeleteSingle(selectedResponse.sessionToken) : void 0
2035
+ onDelete: onDelete && selectedResponse.sessionToken != null ? () => handleDeleteSingle(selectedResponse.sessionToken) : void 0
2047
2036
  }
2048
2037
  ) }) : viewMode === "timeline" ? /* @__PURE__ */ jsx(
2049
2038
  TimelineView,
@@ -2148,26 +2137,11 @@ function ResponseViewerInner({
2148
2137
  }
2149
2138
  );
2150
2139
  }
2151
- var DISPLAY_ONLY_TYPES3 = /* @__PURE__ */ new Set([
2152
- "info-block",
2153
- "info_block",
2154
- "section-header",
2155
- "page-break",
2156
- "image",
2157
- "divider",
2158
- "spacer",
2159
- "video",
2160
- "rich-text",
2161
- "welcome-screen",
2162
- "thank-you-screen",
2163
- "hidden",
2164
- "calculated"
2165
- ]);
2166
2140
  function getAllQuestions(schema) {
2167
2141
  const questions = [];
2168
2142
  for (const section of schema.sections) {
2169
2143
  for (const q of section.questions) {
2170
- if (DISPLAY_ONLY_TYPES3.has(q.type)) continue;
2144
+ if (DISPLAY_ONLY_TYPES.has(q.type)) continue;
2171
2145
  questions.push(q);
2172
2146
  }
2173
2147
  }