@quillsql/react 2.16.45 → 2.16.47

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/dist/index.js CHANGED
@@ -15131,7 +15131,7 @@ function getDateString(value, dateRange, dateBucket, databaseType) {
15131
15131
  });
15132
15132
  }
15133
15133
  function isDateField(fieldType) {
15134
- return fieldType === "date" || fieldType === "datetime" || fieldType === "timestamp" || fieldType === "timestamptz" || fieldType === "MMM_yyyy" || fieldType === "MMM_dd" || fieldType === "MMM_dd_yyyy" || fieldType === "MMM_dd_hh:mm_ap_pm" || fieldType === "hh_ap_pm";
15134
+ return fieldType === "date" || fieldType === "datetime" || fieldType === "timestamp" || fieldType === "timestamptz" || fieldType === "yyyy" || fieldType === "MMM_yyyy" || fieldType === "MMM_dd" || fieldType === "MMM_dd_yyyy" || fieldType === "MMM_dd_hh:mm_ap_pm" || fieldType === "hh_ap_pm";
15135
15135
  }
15136
15136
  var init_dates = __esm({
15137
15137
  "src/utils/dates.ts"() {
@@ -15162,11 +15162,11 @@ function isValidPivot(pivot) {
15162
15162
  reason: 'Value field cannot be empty when aggregation is not "count" or "percentage"'
15163
15163
  };
15164
15164
  } else if (pivot.aggregations?.some(
15165
- (a) => a.aggregationType === "percentage" && !a.valueField && (!pivot.rowField || pivot.columnField)
15165
+ (a) => a.aggregationType === "percentage" && !a.valueField && !pivot.rowField
15166
15166
  )) {
15167
15167
  return {
15168
15168
  valid: false,
15169
- reason: "Empty percentage field only supported for pivot tables with a row field and no column field"
15169
+ reason: "Empty percentage field only supported for pivot tables with a row field"
15170
15170
  };
15171
15171
  }
15172
15172
  return { valid: true, reason: "" };
@@ -16449,7 +16449,7 @@ function formatAggregationTypeForDisplay(aggregationType) {
16449
16449
  case "max":
16450
16450
  return "Max";
16451
16451
  case "percentage":
16452
- return "Percentage";
16452
+ return "Percent";
16453
16453
  default:
16454
16454
  return aggregationType ? capitalize(String(aggregationType)) : "Value";
16455
16455
  }
@@ -16491,6 +16491,21 @@ function getDefaultPivotMeasureLabel(report) {
16491
16491
  return pivot.columnField ? "Count" : `Count ${snakeAndCamelCaseToTitleCase(tableNames[0] ?? "table")}`;
16492
16492
  }
16493
16493
  const target = valueField !== void 0 && String(valueField).trim() ? resolveValueFieldDisplayLabel(report, valueField) : tableNames.length > 0 ? tableNames.map((n) => snakeAndCamelCaseToTitleCase(n)).join(", ") : "table";
16494
+ if (aggregationType.toLowerCase() === "percentage") {
16495
+ const valueField2 = String(
16496
+ fromAggregations?.valueField2 ?? ""
16497
+ ).trim();
16498
+ if (valueField && valueField2) {
16499
+ return `Percent ${target} of ${resolveValueFieldDisplayLabel(report, valueField2)}`;
16500
+ }
16501
+ const valueFieldType = String(
16502
+ fromAggregations?.valueFieldType ?? ""
16503
+ ).trim().toLowerCase();
16504
+ if (valueField && valueFieldType === "bool") {
16505
+ return `Percent ${target}`;
16506
+ }
16507
+ return `Percent of Total ${target}`;
16508
+ }
16494
16509
  return target ? `${aggWord} ${target}` : aggWord;
16495
16510
  }
16496
16511
  function humanizeLegacyPivotMeasureLabelFragment(text) {
@@ -16498,7 +16513,10 @@ function humanizeLegacyPivotMeasureLabelFragment(text) {
16498
16513
  if (!t) return t;
16499
16514
  const m = t.match(LEGACY_AGG_SNAKE_FIELD);
16500
16515
  if (!m) return t;
16501
- return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2])}`;
16516
+ if (String(m[1]).toLowerCase() === "percentage") {
16517
+ return `Percent of Total ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16518
+ }
16519
+ return `${formatAggregationTypeForDisplay(m[1])} ${snakeAndCamelCaseToTitleCase(m[2] ?? "")}`;
16502
16520
  }
16503
16521
  function humanizeChartSeriesTooltipLabel(label) {
16504
16522
  const s = String(label ?? "").trim();
@@ -19973,6 +19991,27 @@ function parseData(rows, queryType) {
19973
19991
  return rows;
19974
19992
  }
19975
19993
  }
19994
+ function isQuillFetchDebugEnabled() {
19995
+ if (typeof globalThis !== "undefined") {
19996
+ for (const debugFlag of QUILL_FETCH_DEBUG_FLAGS) {
19997
+ if (globalThis?.[debugFlag] === true) {
19998
+ return true;
19999
+ }
20000
+ }
20001
+ }
20002
+ if (typeof process !== "undefined" && typeof process.env !== "undefined") {
20003
+ const envCandidates = [
20004
+ process?.env?.QUILL_DEBUG_USEFORM_HYDRATION,
20005
+ process?.env?.QUILL_DEBUG_USEFORM_FILTERS,
20006
+ process?.env?.QUILL_DEBUG_DATA_FETCHER
20007
+ ];
20008
+ return envCandidates.some((candidate) => {
20009
+ const normalized = String(candidate ?? "").trim().toLowerCase();
20010
+ return normalized === "1" || normalized === "true";
20011
+ });
20012
+ }
20013
+ return false;
20014
+ }
19976
20015
  async function testSqlViewState(client, referencedTables, getToken) {
19977
20016
  let errorMessage = null;
19978
20017
  await Promise.all(
@@ -20458,7 +20497,7 @@ async function fetchRelevantInfoFromState(reportBuilderState, tables, columns, a
20458
20497
  return { error: error.message };
20459
20498
  }
20460
20499
  }
20461
- var quillFetch, parseFetchResponse;
20500
+ var QUILL_FETCH_DEBUG_FLAGS, quillFetchDebugSeq, logQuillFetchDebug, quillFetch, parseFetchResponse;
20462
20501
  var init_dataFetcher = __esm({
20463
20502
  "src/utils/dataFetcher.tsx"() {
20464
20503
  "use strict";
@@ -20467,6 +20506,16 @@ var init_dataFetcher = __esm({
20467
20506
  init_tableProcessing();
20468
20507
  init_dates();
20469
20508
  init_changelogNotify();
20509
+ QUILL_FETCH_DEBUG_FLAGS = [
20510
+ "__QUILL_DEBUG_USEFORM_HYDRATION__",
20511
+ "__QUILL_DEBUG_USEFORM_FILTERS__",
20512
+ "__QUILL_DEBUG_DATA_FETCHER__"
20513
+ ];
20514
+ quillFetchDebugSeq = 0;
20515
+ logQuillFetchDebug = (label, payload) => {
20516
+ if (!isQuillFetchDebugEnabled()) return;
20517
+ console.log(`[quillFetch-debug] ${label}`, payload);
20518
+ };
20470
20519
  quillFetch = async ({
20471
20520
  client,
20472
20521
  task,
@@ -20477,7 +20526,24 @@ var init_dataFetcher = __esm({
20477
20526
  urlParameters,
20478
20527
  getToken
20479
20528
  }) => {
20529
+ const debugSeq = ++quillFetchDebugSeq;
20530
+ const debugStart = Date.now();
20531
+ logQuillFetchDebug("request", {
20532
+ seq: debugSeq,
20533
+ task,
20534
+ reportId: metadata?.reportId,
20535
+ dashboardItemId: metadata?.dashboardItemId,
20536
+ hasPivot: Boolean(metadata?.pivot),
20537
+ hasReportBuilderState: Boolean(metadata?.reportBuilderState),
20538
+ filtersLength: Array.isArray(metadata?.filters) ? metadata.filters.length : void 0
20539
+ });
20480
20540
  const token = await getToken();
20541
+ logQuillFetchDebug("token-acquired", {
20542
+ seq: debugSeq,
20543
+ task,
20544
+ ms: Date.now() - debugStart,
20545
+ hasToken: Boolean(token)
20546
+ });
20481
20547
  const queryString = urlParameters ?? `task=${task}`;
20482
20548
  const endpoint = client.queryEndpoint ? `${client.queryEndpoint}?${queryString}` : `${QUILL_SERVER}${QUILL_QUERY_ENDPOINT}?${queryString}`;
20483
20549
  try {
@@ -20509,6 +20575,17 @@ var init_dataFetcher = __esm({
20509
20575
  if (task !== "fetch-changelog-list" && Array.isArray(normalizedData?.changelogs)) {
20510
20576
  notifyChangelogs(normalizedData.changelogs);
20511
20577
  }
20578
+ logQuillFetchDebug("response", {
20579
+ seq: debugSeq,
20580
+ task,
20581
+ ms: Date.now() - debugStart,
20582
+ status: result.status,
20583
+ error: result.error,
20584
+ dataError: normalizedData?.error,
20585
+ queryResultRowCounts: Array.isArray(result.queries?.queryResults) ? result.queries.queryResults.map(
20586
+ (qr) => Array.isArray(qr?.rows) ? qr.rows.length : null
20587
+ ) : void 0
20588
+ });
20512
20589
  return {
20513
20590
  data: normalizedData,
20514
20591
  queries: result.queries,
@@ -20517,8 +20594,19 @@ var init_dataFetcher = __esm({
20517
20594
  };
20518
20595
  } catch (e) {
20519
20596
  if (e instanceof Error && e.name === "AbortError") {
20597
+ logQuillFetchDebug("aborted", {
20598
+ seq: debugSeq,
20599
+ task,
20600
+ ms: Date.now() - debugStart
20601
+ });
20520
20602
  throw e;
20521
20603
  }
20604
+ logQuillFetchDebug("threw", {
20605
+ seq: debugSeq,
20606
+ task,
20607
+ ms: Date.now() - debugStart,
20608
+ message: e instanceof Error ? e.message : String(e)
20609
+ });
20522
20610
  if (task !== "set-section-order") {
20523
20611
  console.error("Failed to fetch:", e);
20524
20612
  }
@@ -21496,6 +21584,7 @@ var numberFormatOptions = [
21496
21584
  "percentage"
21497
21585
  ];
21498
21586
  var dateFormatOptions = [
21587
+ "yyyy",
21499
21588
  "MMM_yyyy",
21500
21589
  "MMM_dd",
21501
21590
  "MMM_dd_yyyy",
@@ -21513,6 +21602,7 @@ var NUMBER_OPTIONS = [
21513
21602
  { value: "percent", label: "percentage" }
21514
21603
  ];
21515
21604
  var DATE_OPTIONS = [
21605
+ { value: "yyyy", label: "year" },
21516
21606
  { value: "MMM_yyyy", label: "month" },
21517
21607
  { value: "MMM_dd", label: "day" },
21518
21608
  { value: "MMM_dd_yyyy", label: "day and year" },
@@ -21900,6 +21990,7 @@ function buildPivotOnlyReportFromResponse({
21900
21990
  rows: rawPivotRows,
21901
21991
  fields: rawPivotFields,
21902
21992
  pivot: effectivePivot,
21993
+ ...effectivePivot.dateBucket ? { dateBucket: effectivePivot.dateBucket } : {},
21903
21994
  client,
21904
21995
  rowCount: typeof response?.rowCount === "number" ? response.rowCount : void 0
21905
21996
  });
@@ -59012,7 +59103,9 @@ var filterTreeToQueryEntry = (node) => {
59012
59103
  };
59013
59104
  };
59014
59105
  var queryRuleToInternalFilter = (rule, fieldConfigByName) => {
59015
- const config = fieldConfigByName[rule.field];
59106
+ const ruleTableName = String(rule.table ?? "").trim();
59107
+ const tableQualifiedConfig = ruleTableName && rule.field && !rule.field.includes(".") ? fieldConfigByName[`${ruleTableName}.${rule.field}`] : void 0;
59108
+ const config = tableQualifiedConfig ?? fieldConfigByName[rule.field];
59016
59109
  if (!config?.fieldType) {
59017
59110
  throw new Error(
59018
59111
  `Missing field type config for "${rule.field}". Add it to fieldConfigByName.`
@@ -59980,7 +60073,8 @@ var AGGREGATION_OPTION_TYPES = [
59980
60073
  "average",
59981
60074
  "count",
59982
60075
  "max",
59983
- "min"
60076
+ "min",
60077
+ "percentage"
59984
60078
  ];
59985
60079
  var QUERY_BUILDER_MULTI_VALUE_OPERATORS = /* @__PURE__ */ new Set(["in", "notin"]);
59986
60080
  var TABLE_FORMAT_CACHE_MAX_ENTRIES = 5e3;
@@ -60470,9 +60564,31 @@ function buildPivotAggregationsWithFieldTables({
60470
60564
  schemaTables,
60471
60565
  preferredTables
60472
60566
  }) || void 0;
60567
+ const isPercentageAggregation = String(aggregation.aggregationType ?? "").toLowerCase() === "percentage";
60568
+ const typeFallbackColumns = [
60569
+ ...sourceReport?.columnInternal ?? sourceReport?.columns ?? []
60570
+ ];
60571
+ const valueFieldType = String(
60572
+ aggregation.valueFieldType ?? ""
60573
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60574
+ field: aggregation.valueField,
60575
+ table: valueFieldTable,
60576
+ schemaTables,
60577
+ fallbackColumns: typeFallbackColumns
60578
+ }) : void 0);
60579
+ const valueField2Type = String(
60580
+ aggregation.valueField2Type ?? ""
60581
+ ).trim() || (isPercentageAggregation ? resolveFieldType({
60582
+ field: aggregation.valueField2,
60583
+ table: valueFieldTable,
60584
+ schemaTables,
60585
+ fallbackColumns: typeFallbackColumns
60586
+ }) : void 0);
60473
60587
  result.push({
60474
60588
  ...aggregationWithoutPivotTables,
60475
60589
  aggregationType: aggregation.aggregationType,
60590
+ ...valueFieldType ? { valueFieldType } : {},
60591
+ ...valueField2Type ? { valueField2Type } : {},
60476
60592
  ...valueFieldTable ? { valueFieldTable } : {}
60477
60593
  });
60478
60594
  }
@@ -60516,8 +60632,9 @@ function getPivotAggregationSortFields(aggregationState, resolvedColumnField) {
60516
60632
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60517
60633
  if (!aggregationType) continue;
60518
60634
  const valueField = String(aggregation.valueField ?? "").trim();
60519
- const canonicalBaseField = valueField || "count";
60520
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60635
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60636
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60637
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60521
60638
  if (!seen.has(sortField)) {
60522
60639
  seen.add(sortField);
60523
60640
  fields.push(sortField);
@@ -60536,8 +60653,9 @@ function getPivotAggregationSortLabelOverrides(aggregationState, aggregationTabl
60536
60653
  const aggregationType = String(aggregation.aggregationType ?? "").trim();
60537
60654
  if (!aggregationType) continue;
60538
60655
  const valueField = String(aggregation.valueField ?? "").trim();
60539
- const canonicalBaseField = valueField || "count";
60540
- const sortField = hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60656
+ const isValuelessPercentage = !valueField && aggregationType.toLowerCase() === "percentage";
60657
+ const canonicalBaseField = valueField || (isValuelessPercentage ? "percentage" : "count");
60658
+ const sortField = isValuelessPercentage ? "percentage" : hasMultipleAggregations && aggregationType.toLowerCase() !== "count" ? `${canonicalBaseField}_${aggregationType}` : canonicalBaseField;
60541
60659
  if (labelBySortField.has(sortField)) continue;
60542
60660
  if (valueField || aggregationType.toLowerCase() !== "count") continue;
60543
60661
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
@@ -60691,6 +60809,9 @@ function trimAggregationSuffix(value, aggregationType) {
60691
60809
  }
60692
60810
  function getAggregationFieldKey(aggregation, hasMultipleAggregations) {
60693
60811
  if (!aggregation.aggregationType) return null;
60812
+ if (!aggregation.valueField && aggregation.aggregationType === "percentage") {
60813
+ return "percentage";
60814
+ }
60694
60815
  if (aggregation.valueField) {
60695
60816
  return hasMultipleAggregations ? `${aggregation.valueField}_${aggregation.aggregationType}` : aggregation.valueField;
60696
60817
  }
@@ -60811,6 +60932,22 @@ function buildAggregationLabel(aggregation, report) {
60811
60932
  }
60812
60933
  const rawTarget = aggregation.valueField ?? (tableNames.length > 0 ? tableNames.join(", ") : "table");
60813
60934
  const target = report ? aggregation.valueField ? resolveValueFieldDisplayLabel(report, aggregation.valueField) : rawTarget : rawTarget;
60935
+ if (String(aggregationType).toLowerCase() === "percentage") {
60936
+ const valueField2 = String(
60937
+ aggregation.valueField2 ?? ""
60938
+ ).trim();
60939
+ if (aggregation.valueField && valueField2) {
60940
+ const denominator = report ? resolveValueFieldDisplayLabel(report, valueField2) : valueField2;
60941
+ return `Percent ${target} of ${denominator}`;
60942
+ }
60943
+ const valueFieldType = String(
60944
+ aggregation.valueFieldType ?? ""
60945
+ ).trim().toLowerCase();
60946
+ if (aggregation.valueField && valueFieldType === "bool") {
60947
+ return `Percent ${target}`;
60948
+ }
60949
+ return `Percent of Total ${target}`;
60950
+ }
60814
60951
  return target ? `${aggWord} ${target}` : aggWord;
60815
60952
  }
60816
60953
  function normalizePivotColumnDisplayValue(value) {
@@ -60851,6 +60988,16 @@ function isPivotTableDateBucketRowAxis(chart, xAxisField) {
60851
60988
  if (!rowField || xf !== rowField) return false;
60852
60989
  return isDateType(String(chart.pivot.rowFieldType ?? ""));
60853
60990
  }
60991
+ var RESOLVABLE_X_AXIS_DATE_BUCKETS = /* @__PURE__ */ new Set(["year", "month", "day"]);
60992
+ function resolvePivotDateBucketXAxisFormat(chart, xAxisField) {
60993
+ if (String(chart?.chartType ?? "").toLowerCase() === "table") return null;
60994
+ const dateBucket = String(chart?.pivot?.dateBucket ?? "").trim().toLowerCase();
60995
+ if (!RESOLVABLE_X_AXIS_DATE_BUCKETS.has(dateBucket)) return null;
60996
+ const rowField = String(chart?.pivot?.rowField ?? "").trim();
60997
+ if (!rowField || String(xAxisField ?? "").trim() !== rowField) return null;
60998
+ if (!isDateType(String(chart?.pivot?.rowFieldType ?? ""))) return null;
60999
+ return getDateFormatFromBucket(dateBucket);
61000
+ }
60854
61001
  function chartDetailRowsMissingPivotRowBucket(chart) {
60855
61002
  if (!chart?.pivot) return false;
60856
61003
  const rowField = String(chart.pivot.rowField ?? "").trim();
@@ -61071,6 +61218,9 @@ function normalizePivotChartForDisplay(chart) {
61071
61218
  withResolvedXAxis.pivot?.rowFieldType ?? ""
61072
61219
  ).trim();
61073
61220
  const pivotDateRowMonthLabelRe = /^[A-Z][a-z]{2} \d{4}$/;
61221
+ const pivotDisplayDateFormat = getDateFormatFromBucket(
61222
+ String(withResolvedXAxis.pivot?.dateBucket ?? "").trim()
61223
+ );
61074
61224
  let displayRows = Array.isArray(
61075
61225
  withResolvedXAxis.rows
61076
61226
  ) ? withResolvedXAxis.rows : [];
@@ -61089,7 +61239,7 @@ function normalizePivotChartForDisplay(chart) {
61089
61239
  }
61090
61240
  const formatted = quillFormat({
61091
61241
  value: raw,
61092
- format: "MMM_yyyy"
61242
+ format: pivotDisplayDateFormat
61093
61243
  });
61094
61244
  if (!formatted || !String(formatted).trim()) {
61095
61245
  return row;
@@ -62133,7 +62283,8 @@ function processPivotState(next, options = {}) {
62133
62283
  const {
62134
62284
  nextState,
62135
62285
  promoteColumnToRow = false,
62136
- fallbackColumnGroupBy
62286
+ fallbackColumnGroupBy,
62287
+ isBoolAggregationField
62137
62288
  } = options;
62138
62289
  const normalizedNext = { ...next };
62139
62290
  const isClearingRowGrouping = nextState?.groupRowsBy !== void 0 && String(nextState.groupRowsBy ?? "").trim().length === 0;
@@ -62176,6 +62327,29 @@ function processPivotState(next, options = {}) {
62176
62327
  normalizedNext.aggregationState = [{ aggregationType: "count" }];
62177
62328
  normalizedNext.aggregationTablesByIndex = [void 0];
62178
62329
  }
62330
+ if (!resolvedRowField) {
62331
+ normalizedNext.aggregationState = normalizedNext.aggregationState.map(
62332
+ (aggregation, index) => {
62333
+ if (String(aggregation.aggregationType ?? "").toLowerCase() !== "percentage") {
62334
+ return aggregation;
62335
+ }
62336
+ const valueField = String(aggregation.valueField ?? "").trim();
62337
+ if (!valueField) {
62338
+ return { ...aggregation, aggregationType: "count" };
62339
+ }
62340
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
62341
+ if (valueField2 && valueField2 !== valueField) {
62342
+ return aggregation;
62343
+ }
62344
+ const table = String(aggregation.valueFieldTable ?? "").trim() || String(normalizedNext.aggregationTablesByIndex[index] ?? "").trim() || void 0;
62345
+ const isBool = isBoolAggregationField?.(valueField, table);
62346
+ if (isBool === false) {
62347
+ return { ...aggregation, aggregationType: "sum" };
62348
+ }
62349
+ return aggregation;
62350
+ }
62351
+ );
62352
+ }
62179
62353
  if (isSettingRowGrouping && normalizedNext.aggregationState.length > 0) {
62180
62354
  normalizedNext.aggregationState = [...normalizedNext.aggregationState];
62181
62355
  }
@@ -63430,6 +63604,19 @@ function useReport(reportIdArg, options = {}) {
63430
63604
  const customFieldsRef = useRef25(schemaData.customFields);
63431
63605
  schemaForReportBuilderStateRef.current = schemaForReportBuilderState;
63432
63606
  customFieldsRef.current = schemaData.customFields;
63607
+ const isBoolAggregationField = useCallback6(
63608
+ (field, table2) => {
63609
+ const fieldType = resolveFieldType({
63610
+ field,
63611
+ table: table2,
63612
+ schemaTables: schemaForReportBuilderStateRef.current
63613
+ });
63614
+ if (!fieldType) return void 0;
63615
+ const normalized = String(fieldType).trim().toLowerCase();
63616
+ return isBoolType(normalized) || normalized === "boolean";
63617
+ },
63618
+ []
63619
+ );
63433
63620
  const schemaForeignKeyMap = useMemo33(
63434
63621
  () => getSchemaForeignKeyMapping(schemaForReportBuilderState),
63435
63622
  [schemaForReportBuilderState]
@@ -64337,9 +64524,10 @@ function useReport(reportIdArg, options = {}) {
64337
64524
  const explicitTable = getPivotGroupOptionTable(targetRaw);
64338
64525
  const field = explicitTable ? decodePivotGroupOptionValue(targetRaw) : targetRaw;
64339
64526
  if (!field) return {};
64527
+ const fieldForTableResolution = field.includes(":") ? String(field.split(":")[0] ?? "").trim() : field;
64340
64528
  const table2 = explicitTable ?? optionTableLookup.get(rawValue) ?? resolveFieldTable({
64341
64529
  report: sourceReport,
64342
- field,
64530
+ field: fieldForTableResolution,
64343
64531
  schemaTables: schemaForReportBuilderState,
64344
64532
  preferredTables
64345
64533
  }) ?? void 0;
@@ -64354,7 +64542,19 @@ function useReport(reportIdArg, options = {}) {
64354
64542
  if (target === "table") {
64355
64543
  return table2 ? { table: table2 } : preferredTables[0] ? { table: preferredTables[0] } : {};
64356
64544
  }
64357
- if (aggregationType !== "count") {
64545
+ if (aggregationType === "percentage" && target.includes(":")) {
64546
+ const [numeratorRaw, denominatorRaw] = target.split(":");
64547
+ const numerator = String(numeratorRaw ?? "").trim();
64548
+ const denominator = String(denominatorRaw ?? "").trim();
64549
+ if (numerator && denominator) {
64550
+ return {
64551
+ valueField: numerator,
64552
+ valueField2: denominator,
64553
+ ...table2 ? { table: table2 } : {}
64554
+ };
64555
+ }
64556
+ }
64557
+ if (aggregationType !== "count" && aggregationType !== "percentage") {
64358
64558
  return {
64359
64559
  valueField: target,
64360
64560
  ...table2 ? { table: table2 } : {}
@@ -64538,7 +64738,9 @@ function useReport(reportIdArg, options = {}) {
64538
64738
  const pivotAggregationsFromSource = Array.isArray(sourceReport.pivot?.aggregations) ? sourceReport.pivot.aggregations : sourceReport.pivot?.aggregationType ? [
64539
64739
  {
64540
64740
  valueField: sourceReport.pivot?.valueField,
64741
+ valueFieldType: sourceReport.pivot?.valueFieldType,
64541
64742
  valueField2: sourceReport.pivot?.valueField2,
64743
+ valueField2Type: sourceReport.pivot?.valueField2Type,
64542
64744
  aggregationType: sourceReport.pivot?.aggregationType
64543
64745
  }
64544
64746
  ] : [];
@@ -64623,7 +64825,8 @@ function useReport(reportIdArg, options = {}) {
64623
64825
  chartType: prev.chartType ?? sourceReport.chartType
64624
64826
  };
64625
64827
  const normalized = processPivotState(nextFromSource, {
64626
- promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag
64828
+ promoteColumnToRow: shouldPromoteColumnToRowFromPendingFlag,
64829
+ isBoolAggregationField
64627
64830
  });
64628
64831
  return normalized;
64629
64832
  }
@@ -64806,6 +65009,19 @@ function useReport(reportIdArg, options = {}) {
64806
65009
  resolveCache.set(fieldName, resolved);
64807
65010
  return resolved;
64808
65011
  }
65012
+ if (isUseFormFiltersDebugEnabled()) {
65013
+ console.error(
65014
+ "[useForm-debug] normalizeQueryBuilderFieldNameForConfig ambiguous",
65015
+ {
65016
+ fieldName,
65017
+ candidateKeys: candidates.map(([key]) => key),
65018
+ preferredTableNames: Array.from(preferredTableNames),
65019
+ primaryTableName,
65020
+ effectiveReportBuilderTableNames,
65021
+ baseReportBuilderTableNames
65022
+ }
65023
+ );
65024
+ }
64809
65025
  return fieldName;
64810
65026
  };
64811
65027
  }, [
@@ -64825,7 +65041,9 @@ function useReport(reportIdArg, options = {}) {
64825
65041
  }
64826
65042
  if (entry && typeof entry === "object" && "field" in entry) {
64827
65043
  const previousFieldName = String(entry.field ?? "").trim();
64828
- const nextFieldName = normalizeQueryBuilderFieldNameForConfig(previousFieldName);
65044
+ const ruleTableName = String(entry.table ?? "").trim();
65045
+ const tableQualifiedKey = ruleTableName && previousFieldName && !previousFieldName.includes(".") ? `${ruleTableName}.${previousFieldName}` : "";
65046
+ const nextFieldName = tableQualifiedKey && queryBuilderFieldConfigByName[tableQualifiedKey] ? tableQualifiedKey : normalizeQueryBuilderFieldNameForConfig(previousFieldName);
64829
65047
  if (!nextFieldName || nextFieldName === previousFieldName) {
64830
65048
  return entry;
64831
65049
  }
@@ -64847,7 +65065,7 @@ function useReport(reportIdArg, options = {}) {
64847
65065
  }
64848
65066
  return normalizeGroup(group);
64849
65067
  };
64850
- }, [normalizeQueryBuilderFieldNameForConfig]);
65068
+ }, [normalizeQueryBuilderFieldNameForConfig, queryBuilderFieldConfigByName]);
64851
65069
  const refreshSelectionTables = useMemo33(() => {
64852
65070
  return [
64853
65071
  getPivotGroupOptionTable(groupRowsBy),
@@ -64948,6 +65166,10 @@ function useReport(reportIdArg, options = {}) {
64948
65166
  aggregation.valueField,
64949
65167
  aggregationTablesByIndex[index]
64950
65168
  );
65169
+ appendPivotColumn(
65170
+ aggregation.valueField2,
65171
+ aggregationTablesByIndex[index]
65172
+ );
64951
65173
  });
64952
65174
  const joinColumns = getJoinColumnsFromTables(effectiveReportBuilderTables);
64953
65175
  const useWideTableColumns = expandReportBuilderColumnsForFlatTable && !pivotState && String(chartType ?? "").toLowerCase() === "table";
@@ -65277,9 +65499,13 @@ function useReport(reportIdArg, options = {}) {
65277
65499
  const aggregationType = String(aggregation.aggregationType);
65278
65500
  const normalizedAggregationType = aggregationType.toLowerCase();
65279
65501
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65280
- const target = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65502
+ const valueField = String(aggregation.valueField ?? "").trim();
65503
+ const valueField2 = String(aggregation.valueField2 ?? "").trim();
65504
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(valueField) && Boolean(valueField2);
65505
+ const target = isPercentageRatio ? `${valueField}:${valueField2}` : valueField ? valueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65506
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(valueField) && isBoolAggregationField(valueField, tableHint || void 0) === true;
65281
65507
  return {
65282
- label: `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65508
+ label: isPercentageRatio ? `Percent ${toTitleCaseLabel(valueField)} of ${toTitleCaseLabel(valueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(target)}` : `Percent of Total ${toTitleCaseLabel(target)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(target)}`,
65283
65509
  value: `${aggregationType}:${target}`
65284
65510
  };
65285
65511
  });
@@ -65287,7 +65513,8 @@ function useReport(reportIdArg, options = {}) {
65287
65513
  aggregationFallbackTableName,
65288
65514
  aggregationFallbackTarget,
65289
65515
  aggregationState,
65290
- aggregationTablesByIndex
65516
+ aggregationTablesByIndex,
65517
+ isBoolAggregationField
65291
65518
  ]);
65292
65519
  const { aggregationBaseOptions, aggregationOptionTableLookup } = useMemo33(() => {
65293
65520
  const joinScoped = fieldOptionsAllowedTableNames ? joinCompatibleSchemaTableNames.filter(
@@ -65313,17 +65540,34 @@ function useReport(reportIdArg, options = {}) {
65313
65540
  if (tableName && !tableLookup.has(countValue)) {
65314
65541
  tableLookup.set(countValue, tableName);
65315
65542
  }
65543
+ if (resolvedGroupRowsBy) {
65544
+ const percentageValue = `percentage:${countTarget}`;
65545
+ if (!seen.has(percentageValue)) {
65546
+ options2.push({
65547
+ value: percentageValue,
65548
+ label: `Percent of Total ${toTitleCaseLabel(countTarget)}`
65549
+ });
65550
+ seen.add(percentageValue);
65551
+ }
65552
+ if (tableName && !tableLookup.has(percentageValue)) {
65553
+ tableLookup.set(percentageValue, tableName);
65554
+ }
65555
+ }
65316
65556
  for (const column of table2.columns ?? []) {
65317
65557
  const fieldName = String(column.field ?? "").trim();
65318
65558
  if (!fieldName) continue;
65559
+ const isBoolColumn = isBoolType(String(column.fieldType ?? "").toLowerCase()) || String(column.jsType ?? "").toLowerCase() === "boolean";
65319
65560
  for (const aggregationType of AGGREGATION_OPTION_TYPES) {
65320
65561
  if (aggregationType === "count") continue;
65321
65562
  if (!canAggregateColumn(column, aggregationType)) continue;
65563
+ const isNumberSharePercentage = aggregationType === "percentage" && !isBoolColumn;
65564
+ if (isNumberSharePercentage && !resolvedGroupRowsBy) continue;
65322
65565
  const value = `${aggregationType}:${fieldName}`;
65323
65566
  if (seen.has(value)) continue;
65567
+ const aggregationWord = aggregationType === "percentage" ? isBoolColumn ? "Percent" : "Percent of Total" : formatAggregationTypeForDisplay(aggregationType);
65324
65568
  options2.push({
65325
65569
  value,
65326
- label: tableName ? `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${formatAggregationTypeForDisplay(aggregationType)} ${toTitleCaseLabel(fieldName)}`
65570
+ label: tableName ? `${aggregationWord} ${toTitleCaseLabel(tableName)} ${toTitleCaseLabel(fieldName)}` : `${aggregationWord} ${toTitleCaseLabel(fieldName)}`
65327
65571
  });
65328
65572
  seen.add(value);
65329
65573
  if (tableName && !tableLookup.has(value)) {
@@ -65346,6 +65590,7 @@ function useReport(reportIdArg, options = {}) {
65346
65590
  cleanedAggregations,
65347
65591
  fieldOptionsAllowedTableNames,
65348
65592
  joinCompatibleSchemaTableNames,
65593
+ resolvedGroupRowsBy,
65349
65594
  schemaForReportBuilderState
65350
65595
  ]);
65351
65596
  const { schemaColumnOptions, columnIdentifierLookup } = useMemo33(() => {
@@ -65588,9 +65833,13 @@ function useReport(reportIdArg, options = {}) {
65588
65833
  aggregation.aggregationType ?? ""
65589
65834
  ).toLowerCase();
65590
65835
  const tableHint = String(aggregationTablesByIndex[index] ?? "").trim();
65591
- const currentTarget = aggregation.valueField && aggregation.valueField.trim().length > 0 ? aggregation.valueField : normalizedAggregationType === "count" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65836
+ const currentValueField = String(aggregation.valueField ?? "").trim();
65837
+ const currentValueField2 = String(aggregation.valueField2 ?? "").trim();
65838
+ const isPercentageRatio = normalizedAggregationType === "percentage" && Boolean(currentValueField) && Boolean(currentValueField2);
65839
+ const currentTarget = isPercentageRatio ? `${currentValueField}:${currentValueField2}` : currentValueField ? currentValueField : normalizedAggregationType === "count" || normalizedAggregationType === "percentage" ? tableHint || aggregationFallbackTableName : aggregationFallbackTarget;
65592
65840
  const currentValue = aggregation.aggregationType ? `${aggregation.aggregationType}:${currentTarget}` : void 0;
65593
- const currentLabel = aggregation.aggregationType ? `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65841
+ const isBoolPercentage = normalizedAggregationType === "percentage" && Boolean(currentValueField) && isBoolAggregationField(currentValueField, tableHint || void 0) === true;
65842
+ const currentLabel = aggregation.aggregationType ? isPercentageRatio ? `Percent ${toTitleCaseLabel(currentValueField)} of ${toTitleCaseLabel(currentValueField2)}` : normalizedAggregationType === "percentage" ? isBoolPercentage ? `Percent ${toTitleCaseLabel(currentTarget)}` : `Percent of Total ${toTitleCaseLabel(currentTarget)}` : `${formatAggregationTypeForDisplay(aggregation.aggregationType)} ${toTitleCaseLabel(currentTarget)}` : "Select aggregation";
65594
65843
  const options2 = currentValue ? baseOptions.some((option) => option.value === currentValue) ? baseOptions : [{ value: currentValue, label: currentLabel }, ...baseOptions] : baseOptions;
65595
65844
  return {
65596
65845
  key: `aggregation-${index}`,
@@ -65604,7 +65853,8 @@ function useReport(reportIdArg, options = {}) {
65604
65853
  aggregationFallbackTarget,
65605
65854
  aggregationState,
65606
65855
  aggregationBaseOptions,
65607
- aggregationTablesByIndex
65856
+ aggregationTablesByIndex,
65857
+ isBoolAggregationField
65608
65858
  ]);
65609
65859
  const nextPivot = useMemo33(() => {
65610
65860
  if (!sourceReport) return null;
@@ -66189,6 +66439,15 @@ function useReport(reportIdArg, options = {}) {
66189
66439
  chartAxisOptionByField.get(resolvedXAxisField)?.format ?? chartAxesBaseChart.xAxisFormat ?? resolvedYAxisFields[0]?.format,
66190
66440
  "string"
66191
66441
  );
66442
+ if (defaultFormat === "string") {
66443
+ const bucketFormat = resolvePivotDateBucketXAxisFormat(
66444
+ chartAxesBaseChart,
66445
+ resolvedXAxisField
66446
+ );
66447
+ if (bucketFormat) {
66448
+ defaultFormat = bucketFormat;
66449
+ }
66450
+ }
66192
66451
  if (isPivotTableDateBucketRowAxis(chartAxesBaseChart, resolvedXAxisField) && chartAxisEdits.xAxisFormat === void 0) {
66193
66452
  defaultFormat = "string";
66194
66453
  }
@@ -67368,10 +67627,12 @@ function useReport(reportIdArg, options = {}) {
67368
67627
  preferredTables
67369
67628
  );
67370
67629
  const existing = next.aggregationState[index];
67630
+ const preservedValueField2 = aggregationType !== "percentage" && existing?.valueField2 ? { valueField2: existing.valueField2 } : {};
67371
67631
  const nextAggregation = {
67372
- ...existing?.valueField2 ? { valueField2: existing.valueField2 } : {},
67632
+ ...preservedValueField2,
67373
67633
  aggregationType,
67374
67634
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67635
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67375
67636
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67376
67637
  };
67377
67638
  if (index === next.aggregationState.length) {
@@ -67408,6 +67669,7 @@ function useReport(reportIdArg, options = {}) {
67408
67669
  {
67409
67670
  aggregationType,
67410
67671
  ...normalizedSelection.valueField ? { valueField: normalizedSelection.valueField } : {},
67672
+ ...normalizedSelection.valueField2 ? { valueField2: normalizedSelection.valueField2 } : {},
67411
67673
  ...normalizedSelection.table ? { valueFieldTable: normalizedSelection.table } : {}
67412
67674
  }
67413
67675
  ];
@@ -67532,7 +67794,8 @@ function useReport(reportIdArg, options = {}) {
67532
67794
  next.chartType = effectiveNextState.chartType || void 0;
67533
67795
  const normalizedNext = processPivotState(next, {
67534
67796
  nextState: effectiveNextState,
67535
- promoteColumnToRow: false
67797
+ promoteColumnToRow: false,
67798
+ isBoolAggregationField
67536
67799
  });
67537
67800
  return normalizedNext;
67538
67801
  }
@@ -67683,6 +67946,17 @@ function useReport(reportIdArg, options = {}) {
67683
67946
  });
67684
67947
  }
67685
67948
  } catch (error) {
67949
+ if (isUseFormFiltersDebugEnabled()) {
67950
+ console.error("[useForm-debug] setFilters swallowed error", {
67951
+ error: error instanceof Error ? error.message : String(error),
67952
+ requestedRules: (nextFilters?.rules ?? []).map((rule) => ({
67953
+ table: rule?.table,
67954
+ field: rule?.field,
67955
+ operator: rule?.operator
67956
+ })),
67957
+ fieldConfigKeys: Object.keys(queryBuilderFieldConfigByName ?? {})
67958
+ });
67959
+ }
67686
67960
  }
67687
67961
  };
67688
67962
  const filterDraft = useReportFilterDraft({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillsql/react",
3
- "version": "2.16.45",
3
+ "version": "2.16.47",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {