Ticket Management Workflow Dashboard with To Do, In Progress, and Resolved columns

Technical Blog Article — App 53: Enterprise Ticket Manager with React, Fluent UI, TypeScript, and Vite

Introduction

Do not manually manipulate the UI.

Update state.
Derive UI from state.
Let React render the result.

  • IT support
  • incident tracking
  • infrastructure requests
  • SharePoint permission requests
  • Microsoft 365 issues
  • onboarding workflows
  • maintenance tracking
  • internal approvals
  • customer service operations

In App 53 — Enterprise Ticket Manager, we build a professional Microsoft-style ticket dashboard using:

  • React
  • TypeScript
  • Vite
  • Fluent UI

This app belongs to Block 3 — Professional Fluent UI Applications, where the ReactLab roadmap evolves from simple component rendering into enterprise-grade architecture and professional Microsoft design patterns.

The goal of this app is not backend integration yet. Instead, the focus is learning:

  • enterprise UI composition
  • Fluent UI dashboard patterns
  • state-driven filtering
  • reusable components
  • derived rendering
  • controlled search interfaces
  • TypeScript modeling
  • scalable React architecture

The most important architectural idea introduced in this app is:

Data
→ React state
→ derived filtering
→ component rendering
→ enterprise dashboard

This reflects the official React mental model:

  • UI is a function of state
  • components render declaratively
  • React handles DOM updates automatically

Official references:


1. Creating the Project

The project starts using Vite with the React TypeScript template.

Why Vite?

Vite is one of the fastest modern frontend development environments because it uses:

  • native ES Modules
  • instant Hot Module Replacement
  • optimized production builds
  • lightweight configuration
  • fast startup performance

Project creation:

mkdir bloco03
cd bloco03
npm create vite@latest app53-ticket-manager -- --template react-ts
cd app53-ticket-manager
npm install

Install Fluent UI:

npm install @fluentui/react-components @fluentui/react-icons

2. Creating the Folder Structure

Enterprise React applications should separate responsibilities clearly.

Create folders:

mkdir src\components
mkdir src\models
mkdir src\data
mkdir src\styles
mkdir src\services

Create files:

New-Item src\models\Ticket.ts -ItemType File
New-Item src\data\tickets.ts -ItemType File
New-Item src\components\TicketCard.tsx -ItemType File
New-Item src\components\TicketDashboard.tsx -ItemType File
New-Item src\components\TicketFilters.tsx -ItemType File
New-Item artigo.md -ItemType File

3. Final Project Structure

app53-ticket-manager/
src/
components/
TicketCard.tsx
TicketDashboard.tsx
TicketFilters.tsx
models/
Ticket.ts
data/
tickets.ts
services/
styles/
App.tsx
main.tsx
index.css

This structure matters because React applications scale quickly. Organizing the architecture early prevents technical debt later.


4. Creating the Ticket Model

src\models\Ticket.ts

export type TicketStatus =
| "Open"
| "In Progress"
| "Resolved";
export type TicketPriority =
| "High"
| "Medium"
| "Low";
export interface Ticket {
id: number;
title: string;
description: string;
assignedTo: string;
department: string;
status: TicketStatus;
priority: TicketPriority;
}

5. Why TypeScript Models Matter

This file defines the shape of the application data.

Each ticket must contain:

  • id
  • title
  • description
  • assigned user
  • department
  • status
  • priority

TypeScript guarantees:

  • predictable objects
  • safer refactoring
  • IDE autocomplete
  • architecture consistency
  • fewer runtime bugs

Without TypeScript, enterprise applications become difficult to maintain as complexity grows.

Official documentation:


6. Creating the Ticket Data Source

src\data\tickets.ts

import type { Ticket } from "../models/Ticket";
export const tickets: Ticket[] = [
{
id: 1,
title: "SharePoint Permission Issue",
description:
"Users cannot access the Finance document library.",
assignedTo: "Amanda Silva",
department: "IT",
status: "Open",
priority: "High",
},
{
id: 2,
title: "Teams Meeting Failure",
description:
"Corporate Teams meetings disconnect unexpectedly.",
assignedTo: "Lucas Mendes",
department: "Infrastructure",
status: "In Progress",
priority: "Medium",
},
{
id: 3,
title: "Power BI Dashboard Update",
description:
"Monthly analytics dashboard requires refresh.",
assignedTo: "Renata Costa",
department: "Business Intelligence",
status: "Resolved",
priority: "Low",
},
];

7. Understanding Data-Driven Rendering

This file introduces one of React’s most important ideas:

The UI should be generated from data.

Instead of manually creating multiple cards:

  • the data array becomes the source of truth
  • React transforms data into UI

This is declarative rendering.

Official documentation:


8. Creating the Ticket Card Component

src\components\TicketCard.tsx

import {
Badge,
Body1,
Card,
CardHeader,
Caption1,
Text,
Title3,
} from "@fluentui/react-components";
import {
CheckmarkCircle24Regular,
Clock24Regular,
Warning24Regular,
} from "@fluentui/react-icons";
import type { Ticket } from "../models/Ticket";
interface TicketCardProps {
ticket: Ticket;
}
function getStatusIcon(status: Ticket["status"]) {
if (status === "Resolved") {
return <CheckmarkCircle24Regular />;
}
if (status === "In Progress") {
return <Clock24Regular />;
}
return <Warning24Regular />;
}
function getBadgeAppearance(status: Ticket["status"]) {
if (status === "Resolved") {
return "filled" as const;
}
if (status === "In Progress") {
return "tint" as const;
}
return "outline" as const;
}
export function TicketCard({
ticket,
}: TicketCardProps) {
return (
<Card
style={{
width: "100%",
padding: "20px",
}}
>
<CardHeader
image={getStatusIcon(ticket.status)}
header={<Title3>{ticket.title}</Title3>}
description={
<Caption1>
Assigned to: {ticket.assignedTo}
</Caption1>
}
/>
<Body1>{ticket.description}</Body1>
<div
style={{
display: "flex",
gap: "12px",
marginTop: "20px",
alignItems: "center",
flexWrap: "wrap",
}}
>
<Badge appearance={getBadgeAppearance(ticket.status)}>
{ticket.status}
</Badge>
<Text size={200}>
Priority: {ticket.priority}
</Text>
<Text size={200}>
Department: {ticket.department}
</Text>
</div>
</Card>
);
}

9. Understanding Component Responsibility

The responsibility of TicketCard is simple:

Receive ticket data
→ render the visual card

The component does not:

  • fetch data
  • own global state
  • filter tickets
  • manage workflows

This is extremely important in React architecture.

Good React components should:

  • have one responsibility
  • remain reusable
  • receive props
  • render UI predictably

Official documentation:


10. Understanding Fluent UI Cards

The Card component is heavily used in enterprise applications because it provides:

  • consistent spacing
  • shadows
  • borders
  • accessibility behavior
  • Microsoft styling

Cards appear everywhere in:

  • dashboards
  • SharePoint portals
  • Microsoft admin interfaces
  • analytics systems
  • CRM dashboards

Official documentation:


11. Understanding Badge Rendering

The ticket status uses Fluent UI Badge.

<Badge appearance={getBadgeAppearance(ticket.status)}>

This creates visual workflow indicators.

Examples:

  • Open → outline
  • In Progress → tint
  • Resolved → filled

Enterprise dashboards often rely heavily on:

  • colors
  • badges
  • status indicators
  • icons

to communicate workflow state quickly.


12. Creating the Filters Component

src\components\TicketFilters.tsx

import {
Dropdown,
Input,
Option,
} from "@fluentui/react-components";
interface TicketFiltersProps {
searchText: string;
statusFilter: string;
onSearchChange: (value: string) => void;
onStatusChange: (value: string) => void;
}
export function TicketFilters({
searchText,
statusFilter,
onSearchChange,
onStatusChange,
}: TicketFiltersProps) {
return (
<div
style={{
display: "flex",
gap: "16px",
marginBottom: "32px",
flexWrap: "wrap",
}}
>
<Input
placeholder="Search tickets..."
value={searchText}
onChange={(_, data) =>
onSearchChange(data.value)
}
/>
<Dropdown
value={statusFilter}
placeholder="Select status"
onOptionSelect={(_, data) =>
onStatusChange(data.optionValue || "")
}
>
<Option value="All">All</Option>
<Option value="Open">Open</Option>
<Option value="In Progress">
In Progress
</Option>
<Option value="Resolved">
Resolved
</Option>
</Dropdown>
</div>
);
}

13. Controlled Components

The filters demonstrate controlled React inputs.

The parent component owns the state:

const [searchText, setSearchText]

and:

const [statusFilter, setStatusFilter]

The filter component only renders the controls and triggers callbacks.

The flow becomes:

User interaction
→ callback executes
→ parent state updates
→ React re-renders
→ dashboard updates

Official documentation:


14. Creating the Dashboard Component

src\components\TicketDashboard.tsx

import { TicketCard } from "./TicketCard";
import type { Ticket } from "../models/Ticket";
interface TicketDashboardProps {
tickets: Ticket[];
}
export function TicketDashboard({
tickets,
}: TicketDashboardProps) {
return (
<div
style={{
display: "grid",
gridTemplateColumns:
"repeat(auto-fit, minmax(320px, 1fr))",
gap: "24px",
}}
>
{tickets.map((ticket) => (
<TicketCard
key={ticket.id}
ticket={ticket}
/>
))}
</div>
);
}

15. Understanding Grid Layout

The dashboard uses CSS Grid:

gridTemplateColumns:
"repeat(auto-fit, minmax(320px, 1fr))"

This creates:

  • responsive columns
  • automatic wrapping
  • enterprise dashboard behavior

The layout automatically adapts to screen size.

This pattern appears constantly in:

  • Microsoft dashboards
  • SharePoint portals
  • analytics systems
  • admin centers

16. Why key={ticket.id} Matters

React lists require stable keys.

key={ticket.id}

helps React:

  • identify list items
  • optimize rendering
  • update the correct elements

Without keys:

  • React warns
  • rendering becomes less predictable

Official documentation:


17. Creating the Root App

src\App.tsx

import { useState } from "react";
import {
FluentProvider,
Text,
Title1,
webLightTheme,
} from "@fluentui/react-components";
import { tickets } from "./data/tickets";
import { TicketDashboard } from "./components/TicketDashboard";
import { TicketFilters } from "./components/TicketFilters";
function App() {
const [searchText, setSearchText] =
useState("");
const [statusFilter, setStatusFilter] =
useState("All");
const filteredTickets = tickets.filter(
(ticket) => {
const matchesSearch =
ticket.title
.toLowerCase()
.includes(searchText.toLowerCase());
const matchesStatus =
statusFilter === "All" ||
ticket.status === statusFilter;
return matchesSearch && matchesStatus;
}
);
return (
<FluentProvider theme={webLightTheme}>
<main
style={{
minHeight: "100vh",
backgroundColor: "#f5f5f5",
padding: "40px",
boxSizing: "border-box",
}}
>
<section
style={{
maxWidth: "1400px",
margin: "0 auto",
}}
>
<Title1>
Enterprise Ticket Manager
</Title1>
<Text>
React + Fluent UI support workflow dashboard.
</Text>
<div
style={{
marginTop: "32px",
}}
>
<TicketFilters
searchText={searchText}
statusFilter={statusFilter}
onSearchChange={setSearchText}
onStatusChange={setStatusFilter}
/>
<TicketDashboard
tickets={filteredTickets}
/>
</div>
</section>
</main>
</FluentProvider>
);
}
export default App;

18. Understanding Derived State

This line is extremely important:

const filteredTickets = tickets.filter(...)

Notice:

  • filtered tickets are NOT stored in state
  • they are derived from existing state

This follows official React guidance:

Avoid redundant state.

Bad approach:

const [filteredTickets, setFilteredTickets]

Good approach:

const filteredTickets = tickets.filter(...)

Why?

  • fewer synchronization bugs
  • cleaner rendering
  • simpler architecture

Official documentation:


19. Why There Is No useEffect

This app intentionally avoids useEffect.

There is:

  • no API synchronization
  • no timers
  • no browser subscriptions
  • no localStorage

The UI is purely derived from:

  • state
  • props
  • filtering logic

Therefore:

useState is enough.

Official documentation:


20. Create main.tsx

src\main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(
document.getElementById("root")!
).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

21. Create index.css

src\index.css

body {
margin: 0;
font-family: "Segoe UI", Arial, sans-serif;
}
* {
box-sizing: border-box;
}

22. Running the Application

Development server:

npm run dev

Production validation:

npm run build

Preview production build:

npm run preview

23. Complete Rendering Flow

main.tsx
renders App
App
owns filter state
TicketFilters
updates filter state
React re-renders App
filteredTickets recalculates
TicketDashboard
receives filtered data
TicketDashboard
maps data into TicketCard components
React updates the UI automatically

This is the React rendering model.


Technical Summary

ConceptExplanation
useStateStores filter state
Derived StateFiltered tickets calculated dynamically
Controlled InputsParent controls filter values
map()Converts arrays into UI
filter()Creates dynamic rendering
Fluent UI CardEnterprise ticket visualization
BadgeWorkflow status indicators
Grid LayoutResponsive dashboard rendering
TypeScript ModelsPredictable architecture
Declarative UIUI derived from state

Concept Table

ConceptFilePurpose
Ticket modelTicket.tsDefines ticket structure
Static datatickets.tsProvides dashboard data
Ticket cardTicketCard.tsxRenders enterprise cards
FiltersTicketFilters.tsxControlled filtering
DashboardTicketDashboard.tsxGrid rendering
Parent stateApp.tsxOwns filter state
Derived renderingApp.tsxDynamic UI updates
Fluent UIAll componentsMicrosoft design system

Official Documentation

React

Fluent UI

Tooling


Final Architectural Insight

App 53 introduces one of the most important enterprise React patterns:

Data
→ derived filtering
→ component composition
→ enterprise dashboard rendering

This architecture appears constantly in:

  • ticket systems
  • SharePoint portals
  • Microsoft dashboards
  • admin centers
  • CRM systems
  • ERP workflows
  • analytics platforms

The key React lesson is:

Do not manually manipulate the UI.
Update state.
Derive UI from state.
Let React render the result.

Current Project Progress

BlockAppNameStatus
Block 101Hello React FluentCompleted
Block 102Profile CardCompleted
Block 103Product ListCompleted
Block 104Microsoft Style User CardCompleted
Block 105Static DashboardCompleted
Block 106Corporate Sidebar MenuCompleted
Block 107Visual Task ListCompleted
Block 108Timeline EventsCompleted
Block 109Employee TableCompleted
Block 110Email ListCompleted
Block 111Grid of CardsCompleted
Block 112Image GalleryCompleted
Block 113Movie CatalogCompleted
Block 114Football TeamsCompleted
Block 115News PageCompleted
Block 116Financial DashboardCompleted
Block 117SharePoint Style LayoutCompleted
Block 118File ExplorerCompleted
Block 119Corporate PortalCompleted
Block 120Microsoft Style Landing PageCompleted
Block 221Modern CounterCompleted
Block 222Toggle ThemeCompleted
Block 223React CalculatorCompleted
Block 224Login FormCompleted
Block 225User RegistrationCompleted
Block 226Complete ToDo ListCompleted
Block 227Shopping ListCompleted
Block 228Product FilterCompleted
Block 229Employee SearchCompleted
Block 230Shopping CartCompleted
Block 231Grade SimulatorCompleted
Block 232Inventory ControlCompleted
Block 233Contact AgendaCompleted
Block 234Currency ConverterCompleted
Block 235BMI CalculatorCompleted
Block 236Installment SimulatorCompleted
Block 237Voting PanelCompleted
Block 238Interactive QuizCompleted
Block 239Team ManagerCompleted
Block 240Dynamic DashboardCompleted
Block 341Microsoft Style LoginCompleted
Block 342Corporate FormCompleted
Block 343Tabs NavigationCompleted
Block 344Dialog ManagerCompleted
Block 345Executive DashboardCompleted
Block 346DataGrid CatalogCompleted
Block 347Enterprise User ListCompleted
Block 348Sidebar NavigationCompleted
Block 349Corporate HeaderCompleted
Block 350Professional ToolbarCompleted
Block 351Notification CenterCompleted
Block 352Administrative PanelCompleted
Block 353Enterprise Ticket ManagerCurrent
Block 354Approval SystemNext

Edvaldo Guimrães Filho Avatar

Published by