Generative Pages liefern in Minuten eine funktionierende Seite. Das Beste: Du kannst den Code anpassen – z. B. D3-Charts, Dark-Mode, bessere Filter und Dataverse-Details (Lookup-/Optionset-Labels). Hier mein persönlicher Weg vom initialen Prompt bis zur finalen, polierten Version mittels Coding.

Mein ursprünglicher Prompt

So habe ich die erste Version erzeugt.

Original Prompt (zum Nachvollziehen)
Create a Generative page that reads from my Dataverse tables br_expenses, br_income, and br_expensecategories and shows a simple budget dashboard with:

Assume columns (map to existing if different):

br_expenses: br_expensesid (PK), br_date (Date Only), br_amount (Currency), br_notes (Text), _br_category_value (Lookup to br_expensecategories)

br_income: br_incomeid (PK), br_date (Date Only), br_amount (Currency), br_source (Text), br_notes (Text)

br_expensecategories: br_expensecategoriesid (PK), br_name (Text)

Top filters (horizontal):

Year dropdown (distinct years from both br_expenses.br_date and br_income.br_date)

Month dropdown (1–12, cascades from Year)

Defaults = current Year and Month; a "Clear filters" button resets both.

Use Dataverse Web API date functions in $filter: Microsoft.Dynamics.CRM.Year(field) and Microsoft.Dynamics.CRM.Month(field) (not year()/month()).

Visuals row:

Pie chart: two slices → "Income" vs "Expenses".

Values: sum(br_income.br_amount) and sum(br_expenses.br_amount) within the selected Year/Month.

Bar chart: Top Expenses (largest amounts).

Axis: category name from $expand of the lookup ($expand=br_category($select=br_name)), or notes if category is empty.

Values: sum of br_expenses.br_amount.

Show Top 10.

Bar chart: Top Incomes (highest amounts).

Axis: br_source (or notes if preferred).

Values: sum of br_income.br_amount.

Show Top 10.

Records section (bottom):

A tabbed grid with Expenses and Income tabs.

Each grid shows: Date, Amount, Category/Source, Notes.

Sorting by Date desc; search box to filter Notes/Source/Category.

Interactions (non-destructive):

Clicking the pie "Income" slice switches to the Income tab; clicking "Expenses" switches to Expenses.

Clicking a bar in Top Expenses sets a selectedCategory UI filter (do not overwrite fetched arrays) and scrolls to the Expenses grid prefiltered to that category.

Clicking a bar in Top Incomes sets a selectedSource UI filter and scrolls to the Income grid prefiltered to that source.

Show a small chip like "Filter: {Category/Source} ×" near the grids to clear that selection.

Data loading specifics:

Build $filter for each table like:
filter = [year ? "Microsoft.Dynamics.CRM.Year(br_date) eq {year}" : "", month ? "Microsoft.Dynamics.CRM.Month(br_date) eq {month}" : ""].filter(Boolean).join(" and ")

Expenses query: $select=br_expensesid,br_date,br_amount,br_notes,_br_category_value and
$expand=br_category($select=br_name) to get the category text.

Income query: $select=br_incomeid,br_date,br_amount,br_source,br_notes.

Sum using Number(...) to avoid string math.

UX details:

Currency formatting via Intl.NumberFormat using environment locale.

Empty states when no data matches the filters.

Responsive layout; avoid fixed SVG sizes (use viewBox).

Do not rely on @OData.Community.Display.V1.FormattedValue for lookup names—use $expand as above.

Warum Generative Pages & trotzdem Code?

Aus einer kurzen Beschreibung entsteht eine Model-Driven-Seite die je nach strukturiertem Prompt besser oder weniger gut aussieht. Für erste Prototypen ist das super, doch wenn es wirklich funktionieren soll, kann man mit ein wenig Code die Seite exat so entwickeln, wie man sie möchte.

Beispiel-Szenario (Dataverse)

  • br_expenses: br_expensename, br_amount, br_date, br_notes, _br_category_value
  • br_income: br_incomename, br_amount, br_date, br_notes, br_source (Optionset)
  • br_expensecategories

Vom Prompt zum Ergebnis – der Verlauf in 4 Schritten

1) Prompt & erste generierte Seite

Ich beschreibe die Seite grob (Datenquellen, drei Visuals, Tabelle) und lasse mir die erste Version generieren. Gut als Start, aber noch roh und nicht wirklich gut.

Generative Pages: Prompt und erste generierte Seite

2) Code-Ansicht öffnen

Jetzt wird's spannend: Ich wechsle in die Code-Ansicht, um Charts, Filter und Grid-Layout anzupassen. Hier kann ich effektiv den ganzen Code direkt im Editor anpassen oder ich kopiere mir den Code in meine bevorzugte Entwicklertools, bei mir Visual Studio Code.

Generative Pages: Code-Editor geöffnet

3) Erster Ausbau mit Code

Nun habe ich den Code ergänzt und mich auf folgendes konzentriert: Donut für Income vs. Expenses (Summen im Zentrum), kompakte Balken für Top Expenses/Incomes und korrekte Formatted Values.

Zwischenergebnis nach Code-Ergänzungen: Charts und Tabelle

4) Finale Version: Dark-Mode + UI-Feinschliff

Zum Schluss noch ein Feature welches ich immer gesehen habe im Zusammenhang mit Genrative Pages Demos, nämlich einem Dark-Mode-Toggle. Ich habe dann noch einige Optimierungen gemacht wie schlankere Abstände und dynamische Tabellenhöhe, damit „Rows per page" visuell an der richtigen Stelle sitzt.

Finale Version mit Dark Mode und UI-Verbesserungen

Wichtige Code-Bausteine

1) Datumsfilter (OData-Range)

const rangeFilter = (year?: number, month?: number) => {
  if (!year) return '';
  const start = month ? new Date(year, month - 1, 1) : new Date(year, 0, 1);
  const end   = month ? new Date(year, month, 1)     : new Date(year + 1, 0, 1);
  return `br_date ge ${start.toISOString()} and br_date lt ${end.toISOString()}`;
};

2) Dataverse laden & Formatted Values

const [exp, inc] = await Promise.all([
  dataApi.queryTable('br_expenses', {
    select: ['br_expensesid','br_date','br_amount','br_notes','br_expensename','_br_category_value'],
    filter
  }),
  dataApi.queryTable('br_income', {
    select: ['br_incomeid','br_date','br_amount','br_notes','br_incomename','br_source'],
    filter
  })
]);

const expenses = exp.rows.map((r:any) => ({
  ...r,
  categoryName: r['_br_category_value@OData.Community.Display.V1.FormattedValue'] || 'Uncategorized'
}));

const income = inc.rows.map((r:any) => ({
  ...r,
  sourceName: r['br_source@OData.Community.Display.V1.FormattedValue'] ||
              (typeof r.br_source === 'string' ? r.br_source : '')
}));

3) Donut „Income vs Expenses" (D3)

// Farben aus MUI-Theme
const data = [
  { label: 'Income',   value: income,   key: 'income',   color: theme.palette.success.main },
  { label: 'Expenses', value: expenses, key: 'expenses', color: theme.palette.warning.main }
].filter(d => d.value > 0);

// arcs zeichnen … und zentrale Text-Summen (Income, Expenses, Net)

4) Kompakte Top-Listen (D3-Bars)

  • Labels bei wenig Platz mit Ellipsis kürzen
  • Werte direkt an den Balken platzieren (keine „Amount"-Achsentitel)
const trim = (s: string, n=22) => (s.length > n ? s.slice(0, n - 1) + '…' : s);

g.append('g') // y-Achse
 .call(d3.axisLeft(y).tickSize(0).tickFormat((d:any) => trim(String(d))));

5) Tabelle: Date vor Notes & Suchfeld neben Tabs

const expenseCols = [
  { field: 'br_expensename', headerName: 'Name',    flex: 1.2 },
  { field: 'categoryName',   headerName: 'Category', flex: 1   },
  { field: 'br_amount',      headerName: 'Amount',   flex: 0.8, valueFormatter: p => fmtMoney(Number(p.value)) },
  { field: 'br_date',        headerName: 'Date',     flex: 0.8, valueFormatter: p => new Date(p.value as any).toLocaleDateString() },
  { field: 'br_notes',       headerName: 'Notes',    flex: 1.6 }
];

6) „Rows per page" clever platzieren

const visibleRows = tab === 'expenses' ? filteredExpenses : filteredIncome;
const fewRows = visibleRows.length <= 6;

<DataGrid
  autoHeight={fewRows}
  rows={visibleRows}
/>

Fazit

Generative Pages sind ein sehr spannendes neues Feature. Mit einem guten Prompt zu Beginn, bekommt man schon schnell einen ersten Entwurf einer Seite. Je nachdem wie der Prompt aufgebaut ist, wird das Ergebnis besser oder weniger. Das beste ist aber, das mit der Option den Code zu ergänzen, man hier mittels Coding seine eigene Seite komplett nach individuellen Wünschen anpassen kann. Für mich das beste Beispiel einer Zusammenarbeit von Low-Code und Pro-Code.

Deine Erfahrungen?

Hast du Fragen oder allenfalls auch schon mit Generative Pages gespielt? Was waren deine Learnings, Stolpersteine oder Aha-Momente? Ich freue mich auf den Austausch – gerne über das Kontaktformular oder auf LinkedIn.

Björn Rindlisbacher
DEINE ERFAHRUNGEN?

Fragen oder eigene Erfahrungen zum Thema? Ich freue mich auf den Austausch — über das Kontaktformular oder auf LinkedIn.