Skip to content

Commit 8bd1550

Browse files
committed
added some changes in cip analytics vs extension
1 parent dcddf48 commit 8bd1550

3 files changed

Lines changed: 290 additions & 52 deletions

File tree

packages/b2c-vs-extension/src/cip-analytics/cip-styles.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,46 @@ body {
509509
grid-column-start: 2;
510510
}
511511

512+
/* === DATE RANGE PRESET WIDGET === */
513+
.date-range-presets {
514+
display: flex;
515+
flex-wrap: wrap;
516+
gap: 8px;
517+
margin-top: 4px;
518+
}
519+
520+
.date-preset-btn {
521+
font-size: 12px;
522+
padding: 4px 14px;
523+
border-radius: 20px;
524+
}
525+
526+
.date-preset-btn.active {
527+
background-color: var(--cip-accent);
528+
color: #fff;
529+
border-color: var(--cip-accent);
530+
}
531+
532+
.date-range-custom {
533+
margin-top: 12px;
534+
}
535+
536+
.date-range-custom--hidden {
537+
display: none;
538+
}
539+
540+
.date-range-custom__fields {
541+
display: grid;
542+
grid-template-columns: 1fr 1fr;
543+
gap: 12px;
544+
}
545+
546+
@media (max-width: 500px) {
547+
.date-range-custom__fields {
548+
grid-template-columns: 1fr;
549+
}
550+
}
551+
512552
.label {
513553
font-size: 12px;
514554
font-weight: 600;
@@ -644,6 +684,13 @@ body {
644684

645685
.btn-secondary:hover:not(:disabled) {
646686
background-color: var(--vscode-button-secondaryHoverBackground);
687+
color: var(--vscode-button-secondaryForeground);
688+
}
689+
690+
.date-preset-btn.active:hover:not(:disabled) {
691+
background-color: var(--cip-accent);
692+
color: #fff;
693+
border-color: var(--cip-accent);
647694
}
648695

649696
.btn-secondary:disabled {

packages/b2c-vs-extension/src/cip-analytics/cip-webview-manager.ts

Lines changed: 111 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ export class CipWebviewManager {
223223
report: CipReportDefinition,
224224
): Promise<void> {
225225
switch (message.command) {
226+
case 'loadSites': {
227+
await this.loadSites(panel);
228+
break;
229+
}
226230
case 'executeQuery': {
227231
await this.executeQuery((message.params ?? {}) as Record<string, string>, panel, report);
228232
break;
@@ -650,6 +654,27 @@ export class CipWebviewManager {
650654
return `${header}\n${dataRows}`;
651655
}
652656

657+
/**
658+
* Fetch the list of site IDs from ccdw_dim_site and send to the webview.
659+
*/
660+
private async loadSites(panel: vscode.WebviewPanel): Promise<void> {
661+
const ctx = this.requireConnectedClient();
662+
if (!ctx) {
663+
panel.webview.postMessage({command: 'sitesLoaded', sites: []});
664+
return;
665+
}
666+
try {
667+
const result = await ctx.client.query(
668+
`SELECT DISTINCT nsite_id FROM ccdw_dim_site WHERE nsite_id IS NOT NULL ORDER BY nsite_id`,
669+
{fetchSize: 500},
670+
);
671+
const sites = result.rows.map((r) => String(r.nsite_id ?? '')).filter(Boolean);
672+
panel.webview.postMessage({command: 'sitesLoaded', sites});
673+
} catch {
674+
panel.webview.postMessage({command: 'sitesLoaded', sites: []});
675+
}
676+
}
677+
653678
/**
654679
* Execute the CIP report query and send results back to webview.
655680
*/
@@ -745,59 +770,93 @@ export class CipWebviewManager {
745770
}
746771

747772
private getReportDashboardContent(webview: vscode.Webview, report: CipReportDefinition): string {
748-
const parameterFields = report.parameters
749-
.map((param) => {
750-
const nameAttr = CipWebviewManager.escapeAttr(param.name);
751-
const descAttr = CipWebviewManager.escapeAttr(param.description);
752-
const descText = CipWebviewManager.escapeHtml(param.description);
753-
const labelText = param.name
754-
.replace(/([A-Z])/g, ' $1')
755-
.trim()
756-
.split(' ')
757-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
758-
.join(' ')
759-
.replace(/\bId\b/g, 'ID');
760-
const nameText = CipWebviewManager.escapeHtml(labelText);
761-
const required = param.required ? 'required' : '';
762-
763-
let inputHtml = '';
764-
// Dates get a `.field--date` modifier so CSS can break them onto their own
765-
// grid row — keeps from/to ranges visually paired instead of wrapping the
766-
// second date to a lonely third row.
767-
let fieldModifier = '';
768-
if (param.type === 'string') {
769-
fieldModifier = ' full';
770-
inputHtml = `<input type="text" id="${nameAttr}" name="${nameAttr}" ${required} class="input" placeholder="${descAttr}" />`;
771-
} else if (param.type === 'date') {
772-
fieldModifier = ' field--date';
773-
inputHtml = `<input type="date" id="${nameAttr}" name="${nameAttr}" ${required} class="input" />`;
774-
} else if (param.type === 'boolean') {
775-
inputHtml = `
776-
<select id="${nameAttr}" name="${nameAttr}" ${required} class="select">
777-
<option value="">— Select —</option>
778-
<option value="true">True</option>
779-
<option value="false">False</option>
780-
</select>
781-
`;
782-
} else if (param.type === 'number') {
783-
const min = param.min !== undefined ? String(param.min) : '';
784-
const max = param.max !== undefined ? String(param.max) : '';
785-
inputHtml = `<input type="number" id="${nameAttr}" name="${nameAttr}" min="${min}" max="${max}" ${required} class="input" placeholder="${descAttr}" />`;
786-
} else {
787-
inputHtml = `<input type="text" id="${nameAttr}" name="${nameAttr}" ${required} class="input" placeholder="${descAttr}" />`;
788-
}
789-
790-
return `
791-
<div class="field${fieldModifier}">
792-
<label class="label" for="${nameAttr}">
793-
${nameText}${param.required ? ' <span class="required">*</span>' : ''}
794-
</label>
795-
<span class="hint">${descText}</span>
796-
${inputHtml}
773+
const hasDateRange =
774+
report.parameters.some((p) => p.name === 'from' && p.type === 'date') &&
775+
report.parameters.some((p) => p.name === 'to' && p.type === 'date');
776+
777+
const dateRangeWidget = hasDateRange
778+
? `
779+
<div class="field full date-range-field">
780+
<label class="label">Date Range <span class="required">*</span></label>
781+
<div class="date-range-presets">
782+
<button type="button" class="btn btn-secondary date-preset-btn" data-preset="last-week">Last Week</button>
783+
<button type="button" class="btn btn-secondary date-preset-btn" data-preset="last-month">Last Month</button>
784+
<button type="button" class="btn btn-secondary date-preset-btn" data-preset="last-6-months">Last 6 Months</button>
785+
<button type="button" class="btn btn-secondary date-preset-btn" data-preset="custom">Custom</button>
786+
</div>
787+
<div class="date-range-custom date-range-custom--hidden" id="dateRangeCustom">
788+
<div class="date-range-custom__fields">
789+
<div class="field">
790+
<label class="label" for="from">From <span class="required">*</span></label>
791+
<input type="date" id="from" name="from" class="input" />
792+
</div>
793+
<div class="field">
794+
<label class="label" for="to">To <span class="required">*</span></label>
795+
<input type="date" id="to" name="to" class="input" />
796+
</div>
797+
</div>
797798
</div>
798-
`;
799-
})
800-
.join('');
799+
<input type="hidden" id="fromHidden" name="from" />
800+
<input type="hidden" id="toHidden" name="to" />
801+
</div>
802+
`
803+
: '';
804+
805+
const parameterFields =
806+
report.parameters
807+
.filter((p) => !(hasDateRange && (p.name === 'from' || p.name === 'to')))
808+
.map((param) => {
809+
const nameAttr = CipWebviewManager.escapeAttr(param.name);
810+
const descAttr = CipWebviewManager.escapeAttr(param.description);
811+
const descText = CipWebviewManager.escapeHtml(param.description);
812+
const labelText = param.name
813+
.replace(/([A-Z])/g, ' $1')
814+
.trim()
815+
.split(' ')
816+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
817+
.join(' ')
818+
.replace(/\bId\b/g, 'ID');
819+
const nameText = CipWebviewManager.escapeHtml(labelText);
820+
const required = param.required ? 'required' : '';
821+
822+
let inputHtml = '';
823+
let fieldModifier = '';
824+
if (param.type === 'string' && param.name === 'siteId') {
825+
fieldModifier = ' full';
826+
inputHtml = `<select id="${nameAttr}" name="${nameAttr}" ${required} class="select site-id-select"><option value="" disabled selected>— Configure connection to load sites —</option></select>`;
827+
} else if (param.type === 'string') {
828+
fieldModifier = ' full';
829+
inputHtml = `<input type="text" id="${nameAttr}" name="${nameAttr}" ${required} class="input" placeholder="${descAttr}" />`;
830+
} else if (param.type === 'date') {
831+
fieldModifier = ' field--date';
832+
inputHtml = `<input type="date" id="${nameAttr}" name="${nameAttr}" ${required} class="input" />`;
833+
} else if (param.type === 'boolean') {
834+
inputHtml = `
835+
<select id="${nameAttr}" name="${nameAttr}" ${required} class="select">
836+
<option value="">— Select —</option>
837+
<option value="true">True</option>
838+
<option value="false">False</option>
839+
</select>
840+
`;
841+
} else if (param.type === 'number') {
842+
const min = param.min !== undefined ? String(param.min) : '';
843+
const max = param.max !== undefined ? String(param.max) : '';
844+
inputHtml = `<input type="number" id="${nameAttr}" name="${nameAttr}" min="${min}" max="${max}" ${required} class="input" placeholder="${descAttr}" />`;
845+
} else {
846+
inputHtml = `<input type="text" id="${nameAttr}" name="${nameAttr}" ${required} class="input" placeholder="${descAttr}" />`;
847+
}
848+
849+
return `
850+
<div class="field${fieldModifier}">
851+
<label class="label" for="${nameAttr}">
852+
${nameText}${param.required ? ' <span class="required">*</span>' : ''}
853+
</label>
854+
<span class="hint">${descText}</span>
855+
${inputHtml}
856+
</div>
857+
`;
858+
})
859+
.join('') + dateRangeWidget;
801860

802861
const displayName = report.name
803862
.split('-')

0 commit comments

Comments
 (0)