Show Details

Imagine walking into an ice cream shop and placing an order. You'd want to see exactly what you ordered, right? Maybe check if you remembered to add those strawberries, or confirm the size you picked. That's exactly what detailed views do in our application - they give customers a complete, beautiful summary of their order that they can access anytime with just a click.
Think of it like the difference between an order ticket and a detailed receipt. The summary card is like a stub - it shows the basics so you can identify the order. But the detailed view is like the full receipt that shows everything: every topping you chose, when you placed the order, and whether it's ready to pick up. It's the complete story of your ice cream order!
In Akan.js, showing detailed views follows a clean architecture pattern. We use three main components that work together:
🎯ViewWrapper (Util.tsx)
A clickable wrapper that triggers the view modal when clicked. Think of it as the "View Details" button functionality.
🖼️ViewModal (Model component)
A modal popup that displays when customers want to see details. It handles opening, closing, and data loading automatically.
📋Detail View (View.tsx)
The actual content inside the modal that displays all the order information in a beautiful, organized layout.
This separation allows each component to have a single responsibility: the wrapper handles clicking, the modal handles the popup behavior, and the view handles the display formatting.

Add View/Edit Modal

Now let's add a View/Edit modal to our ice cream order page. This creates a popup window where customers can see all their order details in an organized format. The modal functions like a detailed receipt that appears when customers want to review their order information.
1
2// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Zone.tsx
3"use client";
4import { Model } from "@akanjs/ui";
5
6// ... existing code ...
7
8interface CardProps {
9  className?: string;
10  init: ClientInit<"icecreamOrder", cnst.LightIcecreamOrder>;
11}
12export const Card = ({ className, init }: CardProps) => {
13  return (
14    <>
15      <Load.Units
16        className={className}
17        init={init}
18        renderItem={(icecreamOrder: cnst.LightIcecreamOrder) => (
19          <IcecreamOrder.Unit.Card
20            key={icecreamOrder.id}
21            href={`/icecreamOrder/${icecreamOrder.id}`}
22            icecreamOrder={icecreamOrder}
23          />
24        )}
25      />
26      <Model.ViewEditModal
27        sliceName="icecreamOrderInPublic"
28        renderTitle={(icecreamOrder: DefaultOf<cnst.IcecreamOrder>) =>
29          `IcecreamOrder - ${icecreamOrder.id ? icecreamOrder.id : "New"}`
30        }
31        renderView={(icecreamOrder: cnst.IcecreamOrder) => (
32          <IcecreamOrder.View.General className="w-full" icecreamOrder={icecreamOrder} />
33        )}
34        renderTemplate={() => <IcecreamOrder.Template.General />}
35      />
36    </>
37  );
38};
This code creates a modal system that handles the display and editing of orders. Let's examine what each part does:
👆
Load.Units Component: Renders all order cards in a list format, with each card displaying basic order information
📱
Model.ViewEditModal: Creates the modal popup that appears when customers click to view details. It automatically loads order data and displays it in a structured format
The ViewEditModal component handles opening, closing, data loading, and content display automatically. You specify what content to show, and it manages the technical implementation. This approach allows you to add detailed views throughout your application with minimal code.

Add View Button to Cards

Now let's add a "View" button to each order card. This button provides a clear interface element that customers can click to access detailed order information. The button will be positioned and styled to integrate with the existing card design.
1
2// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.Unit.tsx
3import { clsx, ModelProps } from "@akanjs/client";
4import { Model } from "@akanjs/ui";
5import { cnst, usePage } from "@koyo/client";
6
7export const Card = ({ icecreamOrder }: ModelProps<"icecreamOrder", cnst.LightIcecreamOrder>) => {
8  const { l } = usePage();
9  return (
10    <div className="group flex h-36 w-full overflow-hidden rounded-xl bg-gradient-to-br from-pink-100 via-yellow-50 to-pink-200 px-8 py-6 shadow-md transition-all duration-300 hover:shadow-xl">
11      <div className="flex w-full flex-col justify-center">
12        <div className="flex items-center gap-2 text-lg font-semibold text-pink-700">
13          <span className="inline-block rounded bg-pink-200 px-2 py-1 text-xs font-bold tracking-wider uppercase">
14            {l.field("icecreamOrder", "id")}
15          </span>
16          <span className="ml-2 font-mono text-pink-900">{icecreamOrder.id}</span>
17        </div>
18        <div className="mt-4 flex items-center gap-2">
19          <span className="inline-block rounded bg-yellow-200 px-2 py-1 text-xs font-bold tracking-wider text-yellow-800 uppercase">
20            {l.field("icecreamOrder", "status")}
21          </span>
22          <span
23            className={clsx("ml-2 rounded-full px-3 py-1 text-sm font-semibold", {
24              "bg-green-100 text-green-700": icecreamOrder.status === "active",
25              "bg-blue-100 text-blue-700": icecreamOrder.status === "processing",
26              "bg-red-100 text-red-700": icecreamOrder.status === "served",
27              "bg-gray-100 text-gray-700": icecreamOrder.status === "canceled",
28            })}
29          >
30            {l.enum("icecreamOrder", "status", icecreamOrder.status)}
31          </span>
32        </div>
33      </div>
34      <div className="bg-base-100/50 flex items-center justify-center gap-2 rounded-xl p-4">
35        <Model.ViewWrapper sliceName="icecreamOrder" modelId={icecreamOrder.id}>
36          <button className="btn btn-primary">
37            <span>{l.trans({ en: "View", ko: "보기" })}</span>
38          </button>
39        </Model.ViewWrapper>
40      </div>
41    </div>
42  );
43};
The key addition here is the ViewWrapper around the button:
🎯
Model.ViewWrapper: This wraps our button and handles the click functionality to show the detailed view
🆔
sliceName and modelId props: We pass the sliceName and modelId so the modal knows which order to display details for
🎨
Button styling: The button uses btn-primary and btn-xl classes for consistent styling across the app

Design Detail View

Now let's create the detailed view component in View.tsx that displays all the ice cream order information in a structured layout. This component will organize and present the order data in a readable format when the modal opens.
1// File: apps/koyo/lib/icecreamOrder/IcecreamOrder.View.tsx
2import { clsx } from "@akanjs/client";
3import { cnst, usePage } from "@koyo/client";
4
5interface IcecreamOrderViewProps {
6  className?: string;
7  icecreamOrder: cnst.IcecreamOrder;
8}
9
10export const General = ({ className, icecreamOrder }: IcecreamOrderViewProps) => {
11  const { l } = usePage();
12  return (
13    <div className={clsx(className, "mx-auto w-full space-y-6 rounded-xl p-8 shadow-lg")}>
14      {/* Header with icon and title */}
15      <div className="flex items-center gap-3 border-b pb-4">
16        <span className="text-3xl font-extrabold text-pink-600">🍦</span>
17        <span className="text-2xl font-bold">{l("icecreamOrder.modelName")}</span>
18        <span className="ml-auto text-xs text-base-content/50">#{icecreamOrder.id}</span>
19      </div>
20      
21      {/* Order details in a grid layout */}
22      <div className="grid grid-cols-2 gap-x-6 gap-y-4">
23        {/* Size information */}
24        <div className="font-semibold text-base-content/50">{l.field("icecreamOrder", "size")}</div>
25        <div>{l.enum("icecreamOrder", "size", icecreamOrder.size.toString())}</div>
26
27        {/* Toppings information */}
28        <div className="font-semibold text-base-content/50">{l.field("icecreamOrder", "toppings")}</div>
29        <div className="flex flex-wrap gap-2">
30          {icecreamOrder.toppings.length === 0 ? (
31            <span className="italic text-gray-400">
32              {l.trans({ en: "No toppings", ko: "토핑 없음" })}
33            </span>
34          ) : (
35            icecreamOrder.toppings.map((topping) => (
36              <span
37                key={topping}
38                className="inline-block rounded-full bg-pink-100 px-2 py-1 text-xs font-medium text-pink-700"
39              >
40                {l.enum("icecreamOrder", "toppings", topping)}
41              </span>
42            ))
43          )}
44        </div>
45
46        {/* Status information */}
47        <div className="font-semibold text-base-content/50">{l.field("icecreamOrder", "status")}</div>
48        <div>
49          <span
50            className={clsx("inline-block rounded-full px-2 py-1 text-xs font-semibold", {
51              "bg-green-100 text-green-700": icecreamOrder.status === "active",
52              "bg-yellow-100 text-yellow-700": icecreamOrder.status === "processing",
53              "bg-red-100 text-red-700": icecreamOrder.status === "served",
54              "bg-gray-100 text-gray-700": icecreamOrder.status === "canceled",
55            })}
56          >
57            {l.enum("icecreamOrder", "status", icecreamOrder.status)}
58          </span>
59        </div>
60
61        {/* Timestamps */}
62        <div className="font-semibold text-base-content/50">{l.field("icecreamOrder", "createdAt")}</div>
63        <div className="text-gray-500">{icecreamOrder.createdAt.format("YYYY-MM-DD HH:mm:ss")}</div>
64
65        <div className="font-semibold text-base-content/50">{l.field("icecreamOrder", "updatedAt")}</div>
66        <div className="text-gray-500">{icecreamOrder.updatedAt.format("YYYY-MM-DD HH:mm:ss")}</div>
67      </div>
68    </div>
69  );
70};
This detailed view component creates a comprehensive display of the ice cream order:
🎨Header Section
Shows an ice cream emoji, the order title from dictionary, and the order ID number for reference
📊Grid Layout
Uses a 2-column grid to organize field labels and values in a clean, scannable format
🏷️Visual Elements
Toppings display as colored badges, status shows with conditional styling, and timestamps are formatted for readability

Test Your Implementation

Let's test the detailed view implementation. Navigate to your ice cream order page and click the "View" button on any order card to verify that the system works correctly.
Testing Steps:
  1. Navigate to http://localhost:4201/icecreamOrder
  2. Create a new ice cream order if you don't have any
  3. Click the 'View' button on any order card
  4. Verify the modal opens with detailed order information
  5. Check that all fields display correctly with proper translations
A modal popup should appear displaying all order details: size, toppings (as colored badges), status (with conditional colors), and timestamps. The modal closes when you click outside it or press the X button.

Best Practices for Detail Views

Here are some important best practices to follow when creating detail views in Akan.js:
Use Dictionary Translations
Always use l.field() and l.enum() for displaying field names and values. This ensures consistency and proper multilingual support.
🎨Consistent Visual Hierarchy
Use grid layouts, consistent spacing, and clear visual separation between different pieces of information.
🔧Reusable Components
Separate the ViewWrapper logic from the actual view content. This allows the wrapper to be reused across different display contexts.
Handle Empty States
Always provide fallback displays for empty or null values, like showing "No toppings" when the toppings array is empty.

What's Next?

You have successfully implemented detailed views for your ice cream orders. Customers can now click on any order to see all the specifics in an organized format. The modal system provides a clean interface for viewing order information.
🎉 What You've Accomplished:
  • Created reusable ViewWrapper components
  • Added view buttons to order cards
  • Designed comprehensive detail views
  • Implemented modal popup functionality
  • Used proper translations and styling
In the next tutorial, we'll add status management functionality that allows shop staff to update orders from "active" to "processing" to "served". This will complete the order workflow system and provide full lifecycle management for ice cream orders.
Released under the MIT License
Official Akan.js Consulting onAkansoft
Copyright © 2025 Akan.js. All rights reserved.
System managed bybassman