If our tables could predict the future?
Source: Dev.to
Predictive Student Grade Tracker
Using Syncfusion React Data Grid + Azure OpenAI
The Idea
During a recent Syncfusion webinar I learned how AI can be woven directly into UI components.
Instead of manually calculating a student’s future GPA, we can let an LLM do the math for us.
Use‑case:
A professor views a dashboard that lists each student’s GPA for Year 1, Year 2, and Year 3.
When the “Calculate Grade” button is pressed, the app should:
- Predict the Year 4 (Final) GPA based on the three previous years.
- Compute the Total GPA as the average of all four years.
- Derive a Total Grade from the Total GPA using the following scale:
| GPA range | Grade |
|---|---|
| 0 – 2.5 | F |
| 2.6 – 2.9 | C |
| 3.0 – 3.4 | B |
| 3.5 – 3.9 | B+ |
| 4.0 – 4.4 | A |
| 4.5 – 5.0 | A+ |
The LLM receives the current grid data and the prompt above, returns a JSON payload, and the grid updates automatically.
Full Implementation (TypeScript)
import { loadCultureFiles } from '../common/culture-loader';
import { Grid, Toolbar, Page, QueryCellInfoEventArgs } from '@syncfusion/ej2-grids';
import { Button } from '@syncfusion/ej2-buttons';
import { predictiveData, predictive } from './data-source';
/* -------------------------------------------------
Predictive Data Entry
------------------------------------------------- */
loadCultureFiles();
Grid.Inject(Page, Toolbar);
const grid = new Grid({
dataSource: predictiveData,
toolbar: [{ template: `Calculate Grade` }],
queryCellInfo: customizeCell,
columns: [
{ field: 'StudentID', isPrimaryKey: true, headerText: 'Student ID', textAlign: 'Right', width: 100 },
{ field: 'StudentName', headerText: 'Student Name', width: 100 },
{ field: 'FirstYearGPA', headerText: 'First Year GPA', textAlign: 'Center', width: 100 },
{ field: 'SecondYearGPA', headerText: 'Second Year GPA', textAlign: 'Center', width: 100 },
{ field: 'ThirdYearGPA', headerText: 'Third Year GPA', textAlign: 'Center', width: 100 },
{ field: 'FinalYearGPA', headerText: 'Final Year GPA', visible: false, textAlign: 'Center', width: 100 },
{ field: 'TotalGPA', headerText: 'Total GPA', visible: false, textAlign: 'Center', width: 100 },
{ field: 'TotalGrade', headerText: 'Total Grade', visible: false, textAlign: 'Center', width: 100 }
],
enableHover: false,
created: onCreated
});
grid.appendTo('#Grid');
/* -------------------------------------------------
Grid lifecycle & UI handlers
------------------------------------------------- */
function onCreated(): void {
const button = document.getElementById('calculate_Grade') as HTMLButtonElement;
button.onclick = calculateGrade;
new Button({ isPrimary: true }, '#calculate_Grade');
}
function calculateGrade(): void {
grid.showSpinner();
executePrompt();
}
/* -------------------------------------------------
Helper utilities
------------------------------------------------- */
function delay(ms: number): Promise {
return new Promise(resolve => setTimeout(resolve, ms));
}
/* -------------------------------------------------
AI interaction
------------------------------------------------- */
function executePrompt(): void {
const prompt = `
Final year GPA column should be updated based on GPA of FirstYearGPA, SecondYearGPA and ThirdYearGPA columns.
Total GPA should update based on average of all years GPA.
Total Grade update based on total GPA.
Grading scale: 0‑2.5 = F, 2.6‑2.9 = C, 3.0‑3.4 = B, 3.5‑3.9 = B+, 4.0‑4.4 = A, 4.5‑5 = A+.
Average value decimal should not exceed 1 digit.
`.trim();
const gridReportJson = JSON.stringify(grid.dataSource);
const userInput = generatePrompt(gridReportJson, prompt);
// Azure OpenAI request (wrapper defined elsewhere)
const aiOutput = (window as any).getAzureChatAIRequest({
messages: [{ role: 'user', content: userInput }]
});
aiOutput.then((result: string) => {
// Strip possible markdown fences returned by the model
const cleanResult = result.replace(/```json/g, '').replace(/```/g, '').trim();
const generatedData: predictive[] = JSON.parse(cleanResult);
grid.hideSpinner();
if (generatedData.length) {
grid.showColumns(['Final Year GPA', 'Total GPA', 'Total Grade']);
updateRows(generatedData);
}
});
}
/* -------------------------------------------------
Grid row update
------------------------------------------------- */
async function updateRows(generatedData: predictive[]): Promise {
await delay(300);
for (const item of generatedData) {
grid.setRowData(item.StudentID, item);
await delay(300);
}
}
/* -------------------------------------------------
Cell styling
------------------------------------------------- */
function customizeCell(args: QueryCellInfoEventArgs): void {
const data = args.data as predictive;
if (args.column!.field === 'FinalYearGPA' || args.column!.field === 'TotalGPA') {
if (data.FinalYearGPA! > 0 || data.TotalGPA! > 0) {
args.cell!.classList.add('e-PredictiveColumn');
}
}
if (args.column!.field === 'TotalGrade') {
if (data.TotalGPA! = 4.5) {
args.cell!.classList.add('e-activecolor');
} else if (data.TotalGPA! > 0) {
args.cell!.classList.add('e-PredictiveColumn');
}
}
}
/* -------------------------------------------------
Prompt generator
------------------------------------------------- */
function generatePrompt(data: string, userInput: string): string {
return `
Given the following datasource bound to the Grid table:
${data}
Return a new datasource based on the user query:
${userInput}
Output **only** valid JSON – no extra text or markdown.
`.trim();
}
How It Works – Step‑by‑Step
- Grid creation – Columns for Student ID, Name, and the three historic GPAs are defined.
- Toolbar button –
created()injects a Calculate Grade button and wires it tocalculateGrade(). - User click –
calculateGrade()shows a spinner and callsexecutePrompt(). - Prompt building –
generatePrompt()concatenates the current grid data (JSON) with a natural‑language instruction for the LLM. - Azure OpenAI call –
getAzureChatAIRequest()sends the prompt; the model returns a JSON payload with the predicted Final GPA, Total GPA, and Grade. - Result handling – The JSON string is cleaned of any markdown fences, parsed, and fed back into the grid via
updateRows(). - Styling –
customizeCell()adds CSS classes so predictive cells stand out (e.g., red for failing grades, green for top grades).
What to Keep in Mind
| Concern | Recommendation |
|---|---|
| Prompt consistency | Keep the instruction short and deterministic; avoid ambiguous wording. |
| JSON validation | Wrap the LLM call in a try/catch and validate the parsed object before updating the UI. |
| Rate limits | Azure OpenAI has request quotas – batch updates or debounce the button if needed. |
| Security | Never expose your Azure OpenAI key on the client; the wrapper (getAzureChatAIRequest) should proxy through a secure backend. |
| Performance | The delay(300) calls give the UI a smooth “typing” feel, but can be removed for production. |
Final Thoughts
By embedding an LLM directly into the Syncfusion Data Grid we turned a static table into a predictive analytics component with just a few lines of code. The same pattern can be reused for any scenario where you need to enrich tabular data on‑the‑fly—sales forecasts, inventory depletion, risk scores, you name it.
Give it a try, tweak the prompt to your domain, and watch your UI become intelligent without building a custom ML model from scratch! 🚀
Summary
- The final output is dynamic data; the 4th column is generated entirely by AI.
Resources
-
GitHub repository:
Syncfusion / smart‑ai‑samples (TypeScript) -
Live demo:
AI Grid – Predictive Entry (Bootstrap 5)