@tailor-platform/erp-kit 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -3
- package/package.json +1 -1
- package/schemas/app-compose/business-flow.yml +3 -0
- package/schemas/app-compose/story.yml +1 -1
- package/skills/erp-kit-app-1-requirements/SKILL.md +6 -12
- package/skills/erp-kit-app-2-breakdown/SKILL.md +3 -10
- package/skills/erp-kit-app-3-doc-review/SKILL.md +1 -5
- package/skills/{erp-kit-app-6-impl-spec → erp-kit-app-4-impl-spec}/SKILL.md +10 -21
- package/skills/erp-kit-app-5-implementation/SKILL.md +149 -0
- package/skills/erp-kit-app-5-implementation/references/backend.md +232 -0
- package/skills/erp-kit-app-5-implementation/references/frontend.md +242 -0
- package/src/module.ts +38 -0
- package/skills/erp-kit-app-1-requirements/references/structure.md +0 -27
- package/skills/erp-kit-app-2-breakdown/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-2-breakdown/references/screen-form.md +0 -139
- package/skills/erp-kit-app-2-breakdown/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-2-breakdown/references/structure.md +0 -27
- package/skills/erp-kit-app-3-doc-review/references/structure.md +0 -27
- package/skills/erp-kit-app-4-design/SKILL.md +0 -256
- package/skills/erp-kit-app-4-design/references/component.md +0 -50
- package/skills/erp-kit-app-4-design/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-4-design/references/screen-form.md +0 -139
- package/skills/erp-kit-app-4-design/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-4-design/references/structure.md +0 -27
- package/skills/erp-kit-app-5-design-review/SKILL.md +0 -290
- package/skills/erp-kit-app-5-design-review/references/component.md +0 -50
- package/skills/erp-kit-app-5-design-review/references/screen-detailview.md +0 -106
- package/skills/erp-kit-app-5-design-review/references/screen-form.md +0 -139
- package/skills/erp-kit-app-5-design-review/references/screen-listview.md +0 -153
- package/skills/erp-kit-app-6-impl-spec/references/auth.md +0 -72
- package/skills/erp-kit-app-6-impl-spec/references/structure.md +0 -27
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# ListView Screen Implementation
|
|
2
|
-
|
|
3
|
-
Implementation pattern for screens with `Screen Type: ListView`.
|
|
4
|
-
Assumes `page.md` and `component.md` rules.
|
|
5
|
-
|
|
6
|
-
## File Structure
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
{screen-path}/
|
|
10
|
-
├── components/
|
|
11
|
-
│ └── {screen-name}-table.tsx # Table component with fragments
|
|
12
|
-
└── page.tsx
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Page Component (page.tsx)
|
|
16
|
-
|
|
17
|
-
Data fetching and `Layout` must be co-located in the same page component.
|
|
18
|
-
Do NOT split into an inner Content component — `Layout` requires `Layout.Column` as direct children.
|
|
19
|
-
|
|
20
|
-
```tsx
|
|
21
|
-
const ResourcesQuery = graphql(
|
|
22
|
-
`
|
|
23
|
-
query Resources {
|
|
24
|
-
resources {
|
|
25
|
-
...ResourceTable
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
`,
|
|
29
|
-
[ResourceTableFragment],
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const ResourcesPage = () => {
|
|
33
|
-
const [{ data, error, fetching }, reexecuteQuery] = useQuery({
|
|
34
|
-
query: ResourcesQuery,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
if (fetching) return <Loading />;
|
|
38
|
-
|
|
39
|
-
if (error || !data) {
|
|
40
|
-
return (
|
|
41
|
-
<ErrorFallback
|
|
42
|
-
title="Failed to load resources"
|
|
43
|
-
message="An error occurred while fetching the list."
|
|
44
|
-
onReset={() => reexecuteQuery({ requestPolicy: "network-only" })}
|
|
45
|
-
/>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<Layout
|
|
51
|
-
columns={1}
|
|
52
|
-
title="Resources"
|
|
53
|
-
actions={[
|
|
54
|
-
<Button key="create" asChild>
|
|
55
|
-
<Link to="create">Create</Link>
|
|
56
|
-
</Button>,
|
|
57
|
-
]}
|
|
58
|
-
>
|
|
59
|
-
<Layout.Column>
|
|
60
|
-
<ResourceTable data={data.resources} />
|
|
61
|
-
</Layout.Column>
|
|
62
|
-
</Layout>
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Table Component (components/{screen-name}-table.tsx)
|
|
68
|
-
|
|
69
|
-
### Fragment Collocation
|
|
70
|
-
|
|
71
|
-
Define a row fragment and a table fragment wrapping the Connection type.
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
const ResourceRowFragment = graphql(`
|
|
75
|
-
fragment ResourceRow on Resource {
|
|
76
|
-
id
|
|
77
|
-
title
|
|
78
|
-
status
|
|
79
|
-
createdAt
|
|
80
|
-
}
|
|
81
|
-
`);
|
|
82
|
-
|
|
83
|
-
export const ResourceTableFragment = graphql(
|
|
84
|
-
`
|
|
85
|
-
fragment ResourceTable on ResourceConnection {
|
|
86
|
-
edges {
|
|
87
|
-
node {
|
|
88
|
-
...ResourceRow
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
`,
|
|
93
|
-
[ResourceRowFragment],
|
|
94
|
-
);
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Row Component
|
|
98
|
-
|
|
99
|
-
```tsx
|
|
100
|
-
const ResourceRow = ({ resource: resourceFragment }: ResourceRowProps) => {
|
|
101
|
-
const resource = readFragment(ResourceRowFragment, resourceFragment);
|
|
102
|
-
return (
|
|
103
|
-
<TableRow>
|
|
104
|
-
<TableCell>{resource.title}</TableCell>
|
|
105
|
-
<TableCell>
|
|
106
|
-
<Badge variant={resource.status === "ACTIVE" ? "default" : "secondary"}>
|
|
107
|
-
{resource.status}
|
|
108
|
-
</Badge>
|
|
109
|
-
</TableCell>
|
|
110
|
-
<TableCell>
|
|
111
|
-
<Button variant="ghost" size="sm" asChild>
|
|
112
|
-
<Link to={resource.id}>View</Link>
|
|
113
|
-
</Button>
|
|
114
|
-
</TableCell>
|
|
115
|
-
</TableRow>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Empty State
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
if (connection.edges.length === 0) {
|
|
124
|
-
return (
|
|
125
|
-
<EmptyState
|
|
126
|
-
title="No resources"
|
|
127
|
-
message="Get started by creating a new resource."
|
|
128
|
-
action={
|
|
129
|
-
<Button asChild>
|
|
130
|
-
<Link to="create">Create</Link>
|
|
131
|
-
</Button>
|
|
132
|
-
}
|
|
133
|
-
/>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Column Property Mapping
|
|
139
|
-
|
|
140
|
-
| Property | Implementation |
|
|
141
|
-
| --------------- | ----------------------------------------------------------- |
|
|
142
|
-
| Hideable: Yes | Column visibility state to toggle show/hide |
|
|
143
|
-
| Sortable: Yes | Sort icon on `<TableHead>` + onClick to toggle query vars |
|
|
144
|
-
| Filterable: Yes | Filter UI above table (`<Select />`) |
|
|
145
|
-
| Searchable: Yes | Search input above table (`<Input placeholder="Search" />`) |
|
|
146
|
-
|
|
147
|
-
## Key Points
|
|
148
|
-
|
|
149
|
-
- Use `<Badge />` for status columns
|
|
150
|
-
- Format dates with `toLocaleDateString()`
|
|
151
|
-
- Use `<EmptyState />` with Create action for empty lists
|
|
152
|
-
- Add a View button per row to navigate to the detail page
|
|
153
|
-
- Iterate data using Connection type `edges.node` pattern
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Application Directory Structure
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
{app_name}/
|
|
5
|
-
├── backend/
|
|
6
|
-
│ ├── src/
|
|
7
|
-
│ │ ├── modules.ts # Declaring module usage
|
|
8
|
-
│ │ ├── modules/
|
|
9
|
-
│ │ │ └── {module-name}/ # Module-specific directory
|
|
10
|
-
│ │ │ ├── resolvers/ # API Definition to expose graphql apis
|
|
11
|
-
│ │ │ └── executors/ # PubSub Automation (one file per declaration)
|
|
12
|
-
│ │ └── generated/ # Auto-generated code (do not edit)
|
|
13
|
-
│ └── tailor.config.ts # tailor application config
|
|
14
|
-
│
|
|
15
|
-
└── frontend/
|
|
16
|
-
└── src/
|
|
17
|
-
├── pages/ # File-based routing (auto-discovered by Vite plugin)
|
|
18
|
-
│ └── {page-path}/
|
|
19
|
-
│ ├── page.tsx
|
|
20
|
-
│ └── {page-path}/
|
|
21
|
-
│ ├── components/
|
|
22
|
-
│ └── page.tsx
|
|
23
|
-
├── components/
|
|
24
|
-
│ └── ui/ # Generic UI components
|
|
25
|
-
├── graphql/ # gql.tada settings
|
|
26
|
-
└── providers/ # react providers
|
|
27
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Application Directory Structure
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
{app_name}/
|
|
5
|
-
├── backend/
|
|
6
|
-
│ ├── src/
|
|
7
|
-
│ │ ├── modules.ts # Declaring module usage
|
|
8
|
-
│ │ ├── modules/
|
|
9
|
-
│ │ │ └── {module-name}/ # Module-specific directory
|
|
10
|
-
│ │ │ ├── resolvers/ # API Definition to expose graphql apis
|
|
11
|
-
│ │ │ └── executors/ # PubSub Automation (one file per declaration)
|
|
12
|
-
│ │ └── generated/ # Auto-generated code (do not edit)
|
|
13
|
-
│ └── tailor.config.ts # tailor application config
|
|
14
|
-
│
|
|
15
|
-
└── frontend/
|
|
16
|
-
└── src/
|
|
17
|
-
├── pages/ # File-based routing (auto-discovered by Vite plugin)
|
|
18
|
-
│ └── {page-path}/
|
|
19
|
-
│ ├── page.tsx
|
|
20
|
-
│ └── {page-path}/
|
|
21
|
-
│ ├── components/
|
|
22
|
-
│ └── page.tsx
|
|
23
|
-
├── components/
|
|
24
|
-
│ └── ui/ # Generic UI components
|
|
25
|
-
├── graphql/ # gql.tada settings
|
|
26
|
-
└── providers/ # react providers
|
|
27
|
-
```
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: erp-kit-app-4-design
|
|
3
|
-
description: Create design mockups from screen specifications using .pen files. Use after completing documentation review with erp-kit-app-3-doc-review. Generates visual UI mockups with shadcn components.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Design Mockup Workflow
|
|
7
|
-
|
|
8
|
-
Create visual UI mockups from Tier 3 screen documentation using .pen design files and shadcn components.
|
|
9
|
-
|
|
10
|
-
## Prerequisites
|
|
11
|
-
|
|
12
|
-
Tier 3 documentation must exist:
|
|
13
|
-
|
|
14
|
-
- `docs/screen/*.md` (screen specifications)
|
|
15
|
-
- Design system source file (contains reusable shadcn components)
|
|
16
|
-
|
|
17
|
-
## Workflow Phases
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
SETUP → ANALYZE → DESIGN → VALIDATE
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Phase 1: Setup
|
|
24
|
-
|
|
25
|
-
Ensure the design system file exists at the application's `docs/design.pen`.
|
|
26
|
-
|
|
27
|
-
This provides access to 87+ shadcn reusable components including:
|
|
28
|
-
|
|
29
|
-
- **Layout**: Sidebar, Card, Table, Data Table
|
|
30
|
-
- **Form**: Input, Textarea, Select, Checkbox, Radio
|
|
31
|
-
- **Navigation**: Breadcrumb, Pagination, Tabs
|
|
32
|
-
- **Feedback**: Badge, Alert, Dialog
|
|
33
|
-
- **Actions**: Button (Default, Secondary, Outline, Ghost, Destructive)
|
|
34
|
-
|
|
35
|
-
### Phase 2: Analyze
|
|
36
|
-
|
|
37
|
-
1. **Read** all screen documentation in `docs/screen/*.md`
|
|
38
|
-
2. **Identify** screen types: ListView, DetailView, Form
|
|
39
|
-
3. **Map** UI requirements to available components
|
|
40
|
-
4. **Plan** layout hierarchy based on sidebar navigation
|
|
41
|
-
|
|
42
|
-
### Phase 3: Design
|
|
43
|
-
|
|
44
|
-
Create mockups using Pencil MCP tools.
|
|
45
|
-
|
|
46
|
-
#### 3.1 Get Editor State
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
mcp__pencil__get_editor_state(include_schema: true)
|
|
50
|
-
mcp__pencil__open_document(filePathOrTemplate: "docs/design.pen")
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
#### 3.2 Discover Components
|
|
54
|
-
|
|
55
|
-
Search for reusable components:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
mcp__pencil__batch_get(
|
|
59
|
-
filePath: "docs/design.pen",
|
|
60
|
-
patterns: [{ "reusable": true }],
|
|
61
|
-
searchDepth: 3
|
|
62
|
-
)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Key component patterns to search:
|
|
66
|
-
|
|
67
|
-
| Component Type | Search Pattern |
|
|
68
|
-
| -------------- | ----------------------- |
|
|
69
|
-
| Sidebar | `{ "name": "Sidebar" }` |
|
|
70
|
-
| Card | `{ "name": "Card" }` |
|
|
71
|
-
| Table | `{ "name": "Table" }` |
|
|
72
|
-
| Input | `{ "name": "Input" }` |
|
|
73
|
-
| Button | `{ "name": "Button" }` |
|
|
74
|
-
| Badge | `{ "name": "Badge" }` |
|
|
75
|
-
|
|
76
|
-
#### 3.3 Create Screen Frames
|
|
77
|
-
|
|
78
|
-
Use `batch_design` to create screens. Standard pattern:
|
|
79
|
-
|
|
80
|
-
```javascript
|
|
81
|
-
// Create screen frame
|
|
82
|
-
screen = I("document", {
|
|
83
|
-
type: "frame",
|
|
84
|
-
name: "{screen-name}",
|
|
85
|
-
x: { x },
|
|
86
|
-
y: { y },
|
|
87
|
-
width: 1280,
|
|
88
|
-
height: 800,
|
|
89
|
-
fill: "$--background",
|
|
90
|
-
layout: "horizontal",
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Add sidebar
|
|
94
|
-
sidebar = I(screen, {
|
|
95
|
-
type: "ref",
|
|
96
|
-
ref: "{sidebar-component-id}",
|
|
97
|
-
height: "fill_container",
|
|
98
|
-
});
|
|
99
|
-
U(sidebar + "/{title-id}", { content: "{app-name}" });
|
|
100
|
-
|
|
101
|
-
// Add main content area
|
|
102
|
-
mainContent = I(screen, {
|
|
103
|
-
type: "frame",
|
|
104
|
-
layout: "vertical",
|
|
105
|
-
gap: 24,
|
|
106
|
-
padding: 32,
|
|
107
|
-
width: "fill_container",
|
|
108
|
-
height: "fill_container",
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
#### 3.4 Layout Organization
|
|
113
|
-
|
|
114
|
-
Arrange screens by navigation hierarchy:
|
|
115
|
-
|
|
116
|
-
| Row | Y Position | Screens |
|
|
117
|
-
| --- | ---------- | ------------------------- |
|
|
118
|
-
| 0 | 0 | Dashboard (Home) |
|
|
119
|
-
| 1 | 1000 | Primary feature screens |
|
|
120
|
-
| 2 | 2000 | Secondary feature screens |
|
|
121
|
-
| 3 | 3000 | Tertiary feature screens |
|
|
122
|
-
| N | N\*1000 | Settings/Admin screens |
|
|
123
|
-
|
|
124
|
-
Flow within rows: List → Detail → Create/Edit Form
|
|
125
|
-
|
|
126
|
-
#### 3.5 Component Patterns by Screen Type
|
|
127
|
-
|
|
128
|
-
**ListView Pattern:**
|
|
129
|
-
|
|
130
|
-
```javascript
|
|
131
|
-
// Header with title and action button
|
|
132
|
-
header = I(mainContent, {
|
|
133
|
-
type: "frame",
|
|
134
|
-
justifyContent: "space_between",
|
|
135
|
-
alignItems: "center",
|
|
136
|
-
width: "fill_container",
|
|
137
|
-
});
|
|
138
|
-
title = I(header, {
|
|
139
|
-
type: "text",
|
|
140
|
-
content: "{page-title}",
|
|
141
|
-
fontSize: 24,
|
|
142
|
-
fontWeight: "600",
|
|
143
|
-
fill: "$--foreground",
|
|
144
|
-
});
|
|
145
|
-
actionBtn = I(header, { type: "ref", ref: "{button-component-id}" });
|
|
146
|
-
|
|
147
|
-
// Filter row
|
|
148
|
-
filterRow = I(mainContent, { type: "frame", gap: 12, alignItems: "center" });
|
|
149
|
-
|
|
150
|
-
// Data table in card
|
|
151
|
-
tableCard = I(mainContent, {
|
|
152
|
-
type: "ref",
|
|
153
|
-
ref: "{card-component-id}",
|
|
154
|
-
width: "fill_container",
|
|
155
|
-
});
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Form Pattern:**
|
|
159
|
-
|
|
160
|
-
```javascript
|
|
161
|
-
// Card with header
|
|
162
|
-
formCard = I(mainContent, {
|
|
163
|
-
type: "ref",
|
|
164
|
-
ref: "{card-component-id}",
|
|
165
|
-
width: "fill_container",
|
|
166
|
-
});
|
|
167
|
-
cardTitle = I(formCard + "/{header-slot-id}", {
|
|
168
|
-
type: "text",
|
|
169
|
-
content: "{form-title}",
|
|
170
|
-
fontSize: 18,
|
|
171
|
-
fontWeight: "600",
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Form fields in content slot
|
|
175
|
-
formContent = I(formCard + "/{content-slot-id}", {
|
|
176
|
-
type: "frame",
|
|
177
|
-
layout: "vertical",
|
|
178
|
-
gap: 16,
|
|
179
|
-
width: "fill_container",
|
|
180
|
-
});
|
|
181
|
-
inputField = I(formContent, {
|
|
182
|
-
type: "ref",
|
|
183
|
-
ref: "{input-component-id}",
|
|
184
|
-
width: "fill_container",
|
|
185
|
-
});
|
|
186
|
-
U(inputField + "/{label-id}", { content: "{field-label}" });
|
|
187
|
-
|
|
188
|
-
// Action buttons in footer slot
|
|
189
|
-
btnRow = I(formCard + "/{footer-slot-id}", {
|
|
190
|
-
type: "frame",
|
|
191
|
-
gap: 12,
|
|
192
|
-
justifyContent: "flex_end",
|
|
193
|
-
});
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**DetailView Pattern:**
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
// Breadcrumb navigation
|
|
200
|
-
bcRow = I(mainContent, { type: "frame", gap: 4, alignItems: "center" });
|
|
201
|
-
|
|
202
|
-
// Info cards
|
|
203
|
-
infoCard = I(mainContent, {
|
|
204
|
-
type: "ref",
|
|
205
|
-
ref: "{card-component-id}",
|
|
206
|
-
width: "fill_container",
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Action section
|
|
210
|
-
actionRow = I(mainContent, { type: "frame", gap: 12 });
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Phase 4: Validate
|
|
214
|
-
|
|
215
|
-
Take screenshots to verify each screen:
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
mcp__pencil__get_screenshot(filePath: "docs/design.pen", nodeId: "{frame-id}")
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Check for:
|
|
222
|
-
|
|
223
|
-
- Consistent sidebar navigation across screens
|
|
224
|
-
- Proper alignment and spacing
|
|
225
|
-
- Correct component usage
|
|
226
|
-
- Readable text and labels
|
|
227
|
-
|
|
228
|
-
## Tips
|
|
229
|
-
|
|
230
|
-
- Always check component structure with `batch_get` before using `U()` updates
|
|
231
|
-
- Use `find_empty_space_on_canvas` for positioning new screens
|
|
232
|
-
- Sidebar navigation should highlight the current screen's menu item
|
|
233
|
-
- Keep operations under 25 per `batch_design` call
|
|
234
|
-
- Use consistent user profile in sidebar footer across all screens
|
|
235
|
-
|
|
236
|
-
## Validation Commands
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
# Check layout structure
|
|
240
|
-
mcp__pencil__snapshot_layout(filePath: "docs/design.pen", maxDepth: 0)
|
|
241
|
-
|
|
242
|
-
# Verify screen screenshot
|
|
243
|
-
mcp__pencil__get_screenshot(filePath: "docs/design.pen", nodeId: "{frame-id}")
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Next Step
|
|
247
|
-
|
|
248
|
-
After completing design mockups, use `/erp-kit-app-5-design-review` to review and validate the mockups against screen specifications.
|
|
249
|
-
|
|
250
|
-
## References
|
|
251
|
-
|
|
252
|
-
- [Application structure](references/structure.md)
|
|
253
|
-
- [ListView screen](references/screen-listview.md)
|
|
254
|
-
- [Form screen](references/screen-form.md)
|
|
255
|
-
- [DetailView screen](references/screen-detailview.md)
|
|
256
|
-
- [Page components](references/component.md)
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Page Components
|
|
2
|
-
|
|
3
|
-
Page-specific components are placed in a `components/` directory, separated from page.tsx.
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
{page-name}/
|
|
7
|
-
├── components/
|
|
8
|
-
│ └── *.tsx
|
|
9
|
-
└── page.tsx
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Fragment Collocation
|
|
13
|
-
|
|
14
|
-
Components define and export their own GraphQL Fragment for the data they display. The parent page imports the Fragment and includes it in the query.
|
|
15
|
-
|
|
16
|
-
Use `graphql`, `FragmentOf`, and `readFragment` from `@/graphql`.
|
|
17
|
-
|
|
18
|
-
```tsx
|
|
19
|
-
// components/user-card.tsx
|
|
20
|
-
import { graphql, type FragmentOf, readFragment } from "@/graphql";
|
|
21
|
-
|
|
22
|
-
export const UserCardFragment = graphql(`
|
|
23
|
-
fragment UserCard on User {
|
|
24
|
-
id
|
|
25
|
-
name
|
|
26
|
-
email
|
|
27
|
-
}
|
|
28
|
-
`);
|
|
29
|
-
|
|
30
|
-
export const UserCard = ({ user }: { user: FragmentOf<typeof UserCardFragment> }) => {
|
|
31
|
-
const data = readFragment(UserCardFragment, user);
|
|
32
|
-
return <div>{data.name}</div>;
|
|
33
|
-
};
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
// page.tsx
|
|
38
|
-
import { UserCard, UserCardFragment } from "./components/user-card";
|
|
39
|
-
|
|
40
|
-
const UserQuery = graphql(
|
|
41
|
-
`
|
|
42
|
-
query User($id: ID!) {
|
|
43
|
-
user(id: $id) {
|
|
44
|
-
...UserCard
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
`,
|
|
48
|
-
[UserCardFragment],
|
|
49
|
-
);
|
|
50
|
-
```
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# DetailView Screen Implementation
|
|
2
|
-
|
|
3
|
-
Implementation pattern for screens with `Screen Type: DetailView`.
|
|
4
|
-
Assumes `page.md` and `component.md` rules.
|
|
5
|
-
|
|
6
|
-
## File Structure
|
|
7
|
-
|
|
8
|
-
```
|
|
9
|
-
{screen-path}/[id]/
|
|
10
|
-
├── components/
|
|
11
|
-
│ ├── {screen-name}-detail.tsx # Main content (left column)
|
|
12
|
-
│ └── {screen-name}-actions.tsx # Action sidebar (right column)
|
|
13
|
-
├── edit/
|
|
14
|
-
│ ├── components/
|
|
15
|
-
│ │ └── edit-{screen-name}-form.tsx
|
|
16
|
-
│ └── page.tsx
|
|
17
|
-
└── page.tsx
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Layout
|
|
21
|
-
|
|
22
|
-
- Two-column layout: main content on the left, actions on the right.
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
const ResourcePage = () => {
|
|
26
|
-
const { id } = useParams();
|
|
27
|
-
const [{ data, error, fetching }, reexecuteQuery] = useQuery({
|
|
28
|
-
query: ResourceQuery,
|
|
29
|
-
variables: { id: id! },
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (fetching) return <Loading />;
|
|
33
|
-
if (error || !data?.resource) return <ErrorFallback ... />;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Layout columns={2} title="Resource Detail">
|
|
37
|
-
<Layout.Column>
|
|
38
|
-
<ResourceDetail resource={data.resource} />
|
|
39
|
-
</Layout.Column>
|
|
40
|
-
<Layout.Column>
|
|
41
|
-
<ResourceActions resource={data.resource} />
|
|
42
|
-
</Layout.Column>
|
|
43
|
-
</Layout>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Left Column: Detail Component
|
|
49
|
-
|
|
50
|
-
Stack `DescriptionCard` and related tables vertically with `space-y-6`.
|
|
51
|
-
|
|
52
|
-
- `DescriptionCard` (`@tailor-platform/app-shell`): renders key-value fields declaratively.
|
|
53
|
-
- Complex content (tables, timelines): wrap in `<div className="rounded-lg border bg-card p-6">`.
|
|
54
|
-
|
|
55
|
-
### DescriptionCard
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
<DescriptionCard
|
|
59
|
-
data={resource}
|
|
60
|
-
title="Overview"
|
|
61
|
-
columns={3}
|
|
62
|
-
fields={[
|
|
63
|
-
{ key: "name", label: "Name", meta: { copyable: true } },
|
|
64
|
-
{
|
|
65
|
-
key: "status",
|
|
66
|
-
label: "Status",
|
|
67
|
-
type: "badge",
|
|
68
|
-
meta: { badgeVariantMap: { ACTIVE: "success", PENDING: "warning" } },
|
|
69
|
-
},
|
|
70
|
-
{ type: "divider" },
|
|
71
|
-
{
|
|
72
|
-
key: "createdAt",
|
|
73
|
-
label: "Created At",
|
|
74
|
-
type: "date",
|
|
75
|
-
meta: { dateFormat: "medium" },
|
|
76
|
-
},
|
|
77
|
-
]}
|
|
78
|
-
/>
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Field types: `"text"` (default), `"badge"`, `"money"`, `"date"`, `"link"`, `"address"`, `"reference"`, `"divider"`
|
|
82
|
-
|
|
83
|
-
## Right Column: Actions Component
|
|
84
|
-
|
|
85
|
-
Wrap in a `Card` component. Use `Button variant="ghost"` for each action item.
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
<Card>
|
|
89
|
-
<CardHeader>
|
|
90
|
-
<CardTitle>Actions</CardTitle>
|
|
91
|
-
</CardHeader>
|
|
92
|
-
<CardContent className="space-y-2">
|
|
93
|
-
<Button variant="ghost" className="w-full justify-start gap-2" asChild>
|
|
94
|
-
<Link to="edit">✎ Edit</Link>
|
|
95
|
-
</Button>
|
|
96
|
-
<Button variant="ghost" className="w-full justify-start gap-2" onClick={handler}>
|
|
97
|
-
✓ Approve
|
|
98
|
-
</Button>
|
|
99
|
-
</CardContent>
|
|
100
|
-
</Card>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- Navigation: `<Button variant="ghost" asChild><Link to="...">`
|
|
104
|
-
- Mutation: `<Button variant="ghost" onClick={handler}>` with custom resolvers (see `backend/resolvers.md`)
|
|
105
|
-
- Conditional: show/hide based on status
|
|
106
|
-
- Multiple cards: stack with `<div className="space-y-6">`
|