Skip to main content

Responsive Layouts, Client Profiles & Composition

One TicketWorkspace UserControl — navigation, ticket grid, ticket detail, activity feed — composed from nested single-purpose containers (Dock shell, FlexLayoutPanel regions, a TableLayoutPanel form, FlowLayoutPanel chips) that re-arranges itself for desktop, tablet and phone when the active Client Profile changes (ClientProfiles.json + Application.ResponsiveProfileChanged), and a reusable SearchBar UserControl used twice instead of two copies of a textbox-and-button. The profile handler toggles panels that already exist; nothing is rebuilt, so the selected ticket, the draft and the search text survive every switch.

Nothing here is deployed anywhere; it is a plain Wisej.NET 4 project that runs on your machine.

Run it

cd "LearnWisej-Samples/Production Architecture Deep Dive\Module 3\TicketOps"
dotnet run -f net10.0 --urls http://localhost:5103

Then open http://localhost:5103. (Visual Studio: open TicketOps.slnx, press F5 — the port is in Properties/launchSettings.json.)

Requirements: .NET 10 SDK, the Wisej-4 4.1.0 NuGet package.

Unlike the other modules this screen is a Page (Application.MainPage) rather than a fixed-size Form: the page fills the browser, so the responsive behaviour can be watched by resizing the real window.

What to try in the Ticket Workspace

ActionWhat you should see
Resize the browser below 1024 px, then below 600 px, then backTablet: the rail shrinks to icons, Assignee disappears, Details / Activity tabs appear. Phone: header Tickets, no rail/toolbar; tap a row → ← Back Ticket #1003. The status bar shows Active profile: …
Select a row, edit it, Savestatus ● Ticket #1003 saved.; the activity feed gains S. Patel updated #1003
Clear the title, Saveorange banner Title is required.
Select #1002 (no hours logged), Closeorange banner Log hours before closing.
Click a chip (Open, High, Mine, Today, Unassigned, Escalated)the grid filters; chips wrap when the list is narrow
Type in the ticket SearchBar / the activity SearchBar, press Enterthe grid / the feed filters — one UserControl, two hosts. One character only: orange banner Type at least 2 characters to search.

Round trip: select #1003, type into Title, resize across the profiles and come back — the text, the selection, the chip and the feed are all still there.

Where things live

TicketOps/
├─ ClientProfiles.json Phone ≤ 600 · Tablet 601–1024 · Desktop ≥ 1025 (browser width; copied to /bin for both targets)
├─ Views/MainPage the Page that fills the browser: status banner + the workspace
├─ Controls/
│ ├─ TicketWorkspace.cs ApplyResponsiveProfile (the switch), ShowAllPanels / CollapseActivityToTab / ShowSingleTaskView, thin handlers
│ ├─ TicketWorkspace.Designer.cs the composition: Dock shell, flexBody, flexSide, tableDetail, flowChips, tabActivity
│ ├─ SearchBar.cs Query · Placeholder · ButtonText · SearchRequested · Clear() — the inner controls stay private
│ ├─ SearchBar.Designer.cs one TableLayoutPanel (100 % box + 84 px button)
│ └─ StatusBanner reusable "● state" + banner UserControl
├─ Services/
│ ├─ ITicketService.cs GetOpenTicketsAsync · SearchAsync(TicketFilter) · SaveAsync · CloseAsync · GetActivityAsync
│ └─ TicketService.cs validation, chip meaning, close rule, persistence orchestration (no UI types, no profile names)
├─ Domain/ Ticket (CanClose / Close), TicketDraft, TicketFilter, TicketEvent, OperationResult
├─ Data/ ITicketRepository + InMemoryTicketRepository (seeded with the tickets and feed)
├─ Infrastructure/ ILog / ActivityLog (server console), AppComposition (one object graph per session)
├─ Resources/Strings.cs safe user-facing messages
├─ docs/ ResponsiveLayoutNotes.md · ProfileNotes.md · ProductionReadinessNote.md · WorkspaceComposition.svg
├─ Default.html + viewport meta tag so phones report their CSS width
├─ Program.cs session entry point → Application.MainPage = AppComposition.CreateMainView()
└─ Startup.cs Kestrel host (app.UseWisej()); *.json is never served

In Visual Studio the same per-profile values (Visible, Width, Dock) could be entered in the Wisej Designer's profile drop-down (Control.ResponsiveProfiles); this sample writes them in ApplyResponsiveProfile so the decision reads as a table in one place.