@gadmin2n/schematics 0.0.140 → 0.0.141

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.
Files changed (46) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +1 -1
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/gen-jwt.ts +8 -1
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +32 -4
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +39 -33
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.spec.ts +34 -1
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +26 -0
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.spec.ts +149 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-compiler.service.ts +20 -6
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.spec.ts +85 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-http-response.ts +157 -0
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.spec.ts +59 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -4
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +125 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +109 -2
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/PageHeader.tsx +46 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +1 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.test.ts +10 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +6 -4
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +30 -2
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +20 -1
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +20 -1
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +9 -20
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +141 -138
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +8 -19
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/human-approval/inbox.tsx +12 -19
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +47 -150
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permissionReadme/index.tsx +13 -12
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +110 -113
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +61 -64
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +187 -200
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/approval-channels/index.tsx +46 -44
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NewNodeDefaults.test.tsx +6 -2
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/connections/index.tsx +50 -41
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +9 -22
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +4 -8
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/index.tsx +9 -22
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.test.tsx +42 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ExternalRequestConfigEditor.tsx +77 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.test.tsx +29 -1
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/ReturnResultConfigEditor.tsx +245 -55
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.test.tsx +152 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +417 -161
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/utils/newNodeConfig.ts +5 -1
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +20 -0
  46. package/package.json +1 -1
@@ -22,9 +22,9 @@ export const Z_INDEX = {
22
22
  OVERLAY: 800,
23
23
  } as const;
24
24
 
25
- /** List / Create 页面顶层组件的内容区样式 */
25
+ /** List / Create 页面顶层组件的内容区样式(页头卡自带 marginBottom: 16,内容区不再加 marginTop) */
26
26
  export const LIST_CONTENT_PROPS = {
27
- style: { marginTop: '1rem' },
27
+ style: { marginTop: 0 },
28
28
  } as const;
29
29
 
30
30
  /** Show 页面顶层组件的内容区样式 */
@@ -39,3 +39,31 @@ export const SHOW_CONTENT_PROPS = {
39
39
  padding: 0,
40
40
  },
41
41
  } as const;
42
+
43
+ /** 自定义页面最外层容器:灰底 + 撑满视口(四边 padding 由外层 layout 承担,页面不写) */
44
+ export const PAGE_WRAPPER_STYLE = {
45
+ background: 'var(--ant-color-bg-layout)',
46
+ minHeight: 'calc(100vh - 64px)',
47
+ } as const;
48
+
49
+ /** 卡片纵向间距:每张内容卡自带 marginBottom: 16 */
50
+ export const CARD_GAP_STYLE = { marginBottom: 16 } as const;
51
+
52
+ /** Header Card:压扁的页面头部卡(配合 PageHeader 使用) */
53
+ export const HEADER_CARD_PROPS = {
54
+ style: { marginBottom: 16 },
55
+ styles: { body: { padding: '12px 24px' } },
56
+ } as const;
57
+
58
+ /** Search Card:压扁的搜索条卡(只有一行输入/筛选,不需要默认 24px 上下留白) */
59
+ export const SEARCH_CARD_PROPS = {
60
+ className: 'card-search-bar',
61
+ style: { marginBottom: 16 },
62
+ styles: { body: { padding: '12px 24px' } },
63
+ } as const;
64
+
65
+ /** 嵌套在内容卡里的子卡片:去边框 + 浅灰底色,避免双层边框 */
66
+ export const NESTED_CARD_PROPS = {
67
+ variant: 'borderless',
68
+ style: { background: 'var(--ant-color-fill-quaternary)' },
69
+ } as const;
@@ -186,12 +186,24 @@
186
186
  "timeoutMilliseconds": "Timeout (milliseconds)"
187
187
  },
188
188
  "returnResult": {
189
+ "type": "Result type",
190
+ "ordinaryData": "Ordinary data",
191
+ "approvalSubjects": "Approver list",
192
+ "approvalSubjectsHint": "Only for a Workflow that automatically finds approvers",
189
193
  "source": "What to return",
190
194
  "previousNode": "Return the previous node result",
191
195
  "previousNodeHint": "Returns the result connected to this node. No node ID or path is required.",
192
196
  "workflowData": "Return a field from Workflow data",
193
197
  "workflowPath": "Workflow data field",
194
- "workflowHint": "Enter a relative path, for example approvers. It will be read as workflow.approvers."
198
+ "workflowHint": "Enter a relative path, for example body. It will be read from Workflow start data.",
199
+ "httpResponse": "HTTP response",
200
+ "httpStatusCode": "HTTP status code",
201
+ "httpHeaders": "Response headers",
202
+ "headerName": "Header name",
203
+ "headerValue": "Header value",
204
+ "addHeader": "Add response header",
205
+ "deleteHeader": "Delete header {{name}}",
206
+ "httpHint": "The returned body is the data selected above. Status and headers are optional."
195
207
  },
196
208
  "connection": {
197
209
  "title": "Connections",
@@ -272,6 +284,13 @@
272
284
  "endpointLoadFailed": "Unable to generate the external request address. Configure the server's public Workflow address or network interface.",
273
285
  "copyEndpoint": "Copy endpoint",
274
286
  "copied": "Copied",
287
+ "response": "How the call finishes",
288
+ "respondImmediately": "Respond immediately",
289
+ "respondImmediatelyHint": "Return 202 after starting the Workflow",
290
+ "waitForResult": "Wait for Workflow result",
291
+ "waitForResultHint": "Keep this request open until a Return Result node is reached",
292
+ "responseTimeout": "Maximum wait time",
293
+ "seconds": "seconds",
275
294
  "verification": "Request verification",
276
295
  "signed": "Secure signature",
277
296
  "signedHint": "For systems that can generate the documented signature",
@@ -186,12 +186,24 @@
186
186
  "timeoutMilliseconds": "超时时间(毫秒)"
187
187
  },
188
188
  "returnResult": {
189
+ "type": "结果类型",
190
+ "ordinaryData": "普通数据",
191
+ "approvalSubjects": "审批人列表",
192
+ "approvalSubjectsHint": "仅用于自动查找审批人的 Workflow",
189
193
  "source": "返回什么",
190
194
  "previousNode": "返回上一步节点的结果",
191
195
  "previousNodeHint": "直接把连接到这里的上一步结果返回给调用方,不需要填写节点 ID 或路径。",
192
196
  "workflowData": "返回 Workflow 启动数据中的字段",
193
197
  "workflowPath": "Workflow 数据字段",
194
- "workflowHint": "填写相对路径,例如 approvers;系统会读取 workflow.approvers。"
198
+ "workflowHint": "填写相对路径,例如 body;系统会从 Workflow 启动数据中读取。",
199
+ "httpResponse": "HTTP 响应",
200
+ "httpStatusCode": "HTTP 状态码",
201
+ "httpHeaders": "响应头",
202
+ "headerName": "响应头名称",
203
+ "headerValue": "响应头内容",
204
+ "addHeader": "添加响应头",
205
+ "deleteHeader": "删除响应头 {{name}}",
206
+ "httpHint": "响应正文就是上面选择的数据;状态码和响应头是可选设置。"
195
207
  },
196
208
  "connection": {
197
209
  "title": "连接管理",
@@ -272,6 +284,13 @@
272
284
  "endpointLoadFailed": "无法生成外部请求地址,请检查服务器 Workflow 公共地址或网卡配置。",
273
285
  "copyEndpoint": "复制请求地址",
274
286
  "copied": "已复制",
287
+ "response": "调用如何结束",
288
+ "respondImmediately": "立即返回",
289
+ "respondImmediatelyHint": "启动 Workflow 后立即返回 202",
290
+ "waitForResult": "等待 Workflow 返回结果",
291
+ "waitForResultHint": "保持本次请求,直到流程执行到“返回结果”节点",
292
+ "responseTimeout": "最长等待时间",
293
+ "seconds": "秒",
275
294
  "verification": "请求验证",
276
295
  "signed": "安全签名",
277
296
  "signedHint": "适合能够按照说明生成签名的系统",
@@ -29,8 +29,10 @@ import {
29
29
  } from '@ant-design/icons';
30
30
  import { useSearchParams, useNavigate } from 'react-router-dom';
31
31
  import { customRequest } from 'helpers/http';
32
+ import { PageHeader } from 'components';
33
+ import { PAGE_WRAPPER_STYLE } from 'constants/layout';
32
34
 
33
- const { Title, Text } = Typography;
35
+ const { Text } = Typography;
34
36
 
35
37
  // ─── Types ────────────────────────────────────────────────────────────────────
36
38
 
@@ -456,24 +458,11 @@ export default function AgendaJobsPage() {
456
458
  // ─── Render ────────────────────────────────────────────────────────────────
457
459
 
458
460
  return (
459
- <div
460
- style={{
461
- background: 'var(--ant-color-bg-layout)',
462
- minHeight: 'calc(100vh - 64px)',
463
- }}
464
- >
461
+ <div style={PAGE_WRAPPER_STYLE}>
465
462
  {/* Header */}
466
- <Card style={{ marginBottom: 16 }}>
467
- <div
468
- style={{
469
- display: 'flex',
470
- justifyContent: 'space-between',
471
- alignItems: 'center',
472
- }}
473
- >
474
- <Title level={4} style={{ margin: 0 }}>
475
- Cron Jobs
476
- </Title>
463
+ <PageHeader
464
+ title="Cron Jobs"
465
+ extra={
477
466
  <Space>
478
467
  <Tooltip title="查看 AgendaJob 表原始数据">
479
468
  <Button
@@ -498,8 +487,8 @@ export default function AgendaJobsPage() {
498
487
  )}
499
488
  <Button icon={<ReloadOutlined />} onClick={fetchJobs}></Button>
500
489
  </Space>
501
- </div>
502
- </Card>
490
+ }
491
+ />
503
492
 
504
493
  {/* Metrics cards */}
505
494
  {metricsOverview && (
@@ -10,6 +10,7 @@ import { getDefaultFilter, HttpError } from '@refinedev/core';
10
10
 
11
11
  import { SearchOutlined } from '@ant-design/icons';
12
12
  import {
13
+ Card,
13
14
  DatePicker,
14
15
  Input,
15
16
  Radio,
@@ -75,145 +76,147 @@ export const AuditLogPage = () => {
75
76
  canCreate={false}
76
77
  contentProps={LIST_CONTENT_PROPS}
77
78
  >
78
- <Table
79
- className="audit-log-table"
80
- {...tableProps}
81
- rowKey="id"
82
- scroll={{ x: true }}
83
- pagination={{
84
- ...tableProps.pagination,
85
- showTotal: (total) => (
86
- <PaginationTotal total={total} entityName="audit logs" />
87
- ),
88
- }}
89
- >
90
- <Table.Column
91
- dataIndex="authorName"
92
- title="User"
93
- width="15%"
94
- filterIcon={<SearchOutlined />}
95
- render={(_, record: AuditLog) => {
96
- return (
97
- <Space>
98
- <CustomAvatar
99
- src={`https://rhrc.woa.com/photo/150/${record.authorName}.png`}
100
- name={record.authorName}
101
- />
102
- {record.authorName || 'N/A'}
103
- </Space>
104
- );
79
+ <Card className="card-table">
80
+ <Table
81
+ className="audit-log-table"
82
+ {...tableProps}
83
+ rowKey="id"
84
+ scroll={{ x: true }}
85
+ pagination={{
86
+ ...tableProps.pagination,
87
+ showTotal: (total) => (
88
+ <PaginationTotal total={total} entityName="audit logs" />
89
+ ),
105
90
  }}
106
- filterDropdown={(props) => (
107
- <FilterDropdown
108
- {...(props as any)}
109
- clearFilters={() => {
110
- props.setSelectedKeys([]);
111
- props.confirm?.();
112
- }}
113
- >
114
- <Input />
115
- </FilterDropdown>
116
- )}
117
- defaultFilteredValue={getDefaultFilter(
118
- 'authorName',
119
- filters,
120
- 'contains',
121
- )}
122
- />
123
- <Table.Column
124
- dataIndex="action"
125
- title="Action"
126
- render={(_, record: AuditLog) => {
127
- return (
128
- <Space>
129
- <Tag color={getActionColor(record.action)}>
130
- {
131
- auditLogActionOptions.find(
132
- (option) => option.value === record.action,
133
- )?.label
134
- }
135
- </Tag>
136
- </Space>
137
- );
138
- }}
139
- filterDropdown={(props) => (
140
- <FilterDropdown
141
- {...(props as any)}
142
- clearFilters={() => {
143
- props.setSelectedKeys([]);
144
- props.confirm?.();
145
- }}
146
- >
147
- <Radio.Group>
148
- {auditLogActionOptions.map((option) => (
149
- <Radio key={option.value} value={option.value}>
150
- {option.label}
151
- </Radio>
152
- ))}
153
- </Radio.Group>
154
- </FilterDropdown>
155
- )}
156
- defaultFilteredValue={getDefaultFilter('action', filters, 'eq')}
157
- />
158
- <Table.Column
159
- dataIndex="resource"
160
- title="Entity"
161
- filterIcon={<SearchOutlined />}
162
- filterDropdown={(props) => (
163
- <FilterDropdown
164
- {...(props as any)}
165
- clearFilters={() => {
166
- props.setSelectedKeys([]);
167
- props.confirm?.();
168
- }}
169
- >
170
- <Input />
171
- </FilterDropdown>
172
- )}
173
- defaultFilteredValue={getDefaultFilter(
174
- 'resource',
175
- filters,
176
- 'contains',
177
- )}
178
- />
179
- <Table.Column dataIndex="resourceId" title="Entity ID" />
180
- <Table.Column
181
- dataIndex="data"
182
- title="Changes"
183
- render={(_, record: AuditLog) => <ActionCell record={record} />}
184
- />
185
- <Table.Column
186
- dataIndex="timestamp"
187
- title="Date & Time"
188
- width="15%"
189
- render={(value) => (
190
- <DateField
191
- style={{ verticalAlign: 'middle' }}
192
- value={value}
193
- format="YYYY-MM-DD hh:mm:ss"
194
- />
195
- )}
196
- filterDropdown={(props) => (
197
- <FilterDropdown
198
- {...(props as any)}
199
- mapValue={rangePickerFilterMapper}
200
- clearFilters={() => {
201
- props.setSelectedKeys([]);
202
- props.confirm?.();
203
- }}
204
- >
205
- <DatePicker.RangePicker />
206
- </FilterDropdown>
207
- )}
208
- sorter
209
- defaultFilteredValue={getDefaultFilter(
210
- 'timestamp',
211
- filters,
212
- 'between',
213
- )}
214
- defaultSortOrder={getDefaultSortOrder('timestamp', sorters)}
215
- />
216
- </Table>
91
+ >
92
+ <Table.Column
93
+ dataIndex="authorName"
94
+ title="User"
95
+ width="15%"
96
+ filterIcon={<SearchOutlined />}
97
+ render={(_, record: AuditLog) => {
98
+ return (
99
+ <Space>
100
+ <CustomAvatar
101
+ src={`https://rhrc.woa.com/photo/150/${record.authorName}.png`}
102
+ name={record.authorName}
103
+ />
104
+ {record.authorName || 'N/A'}
105
+ </Space>
106
+ );
107
+ }}
108
+ filterDropdown={(props) => (
109
+ <FilterDropdown
110
+ {...(props as any)}
111
+ clearFilters={() => {
112
+ props.setSelectedKeys([]);
113
+ props.confirm?.();
114
+ }}
115
+ >
116
+ <Input />
117
+ </FilterDropdown>
118
+ )}
119
+ defaultFilteredValue={getDefaultFilter(
120
+ 'authorName',
121
+ filters,
122
+ 'contains',
123
+ )}
124
+ />
125
+ <Table.Column
126
+ dataIndex="action"
127
+ title="Action"
128
+ render={(_, record: AuditLog) => {
129
+ return (
130
+ <Space>
131
+ <Tag color={getActionColor(record.action)}>
132
+ {
133
+ auditLogActionOptions.find(
134
+ (option) => option.value === record.action,
135
+ )?.label
136
+ }
137
+ </Tag>
138
+ </Space>
139
+ );
140
+ }}
141
+ filterDropdown={(props) => (
142
+ <FilterDropdown
143
+ {...(props as any)}
144
+ clearFilters={() => {
145
+ props.setSelectedKeys([]);
146
+ props.confirm?.();
147
+ }}
148
+ >
149
+ <Radio.Group>
150
+ {auditLogActionOptions.map((option) => (
151
+ <Radio key={option.value} value={option.value}>
152
+ {option.label}
153
+ </Radio>
154
+ ))}
155
+ </Radio.Group>
156
+ </FilterDropdown>
157
+ )}
158
+ defaultFilteredValue={getDefaultFilter('action', filters, 'eq')}
159
+ />
160
+ <Table.Column
161
+ dataIndex="resource"
162
+ title="Entity"
163
+ filterIcon={<SearchOutlined />}
164
+ filterDropdown={(props) => (
165
+ <FilterDropdown
166
+ {...(props as any)}
167
+ clearFilters={() => {
168
+ props.setSelectedKeys([]);
169
+ props.confirm?.();
170
+ }}
171
+ >
172
+ <Input />
173
+ </FilterDropdown>
174
+ )}
175
+ defaultFilteredValue={getDefaultFilter(
176
+ 'resource',
177
+ filters,
178
+ 'contains',
179
+ )}
180
+ />
181
+ <Table.Column dataIndex="resourceId" title="Entity ID" />
182
+ <Table.Column
183
+ dataIndex="data"
184
+ title="Changes"
185
+ render={(_, record: AuditLog) => <ActionCell record={record} />}
186
+ />
187
+ <Table.Column
188
+ dataIndex="timestamp"
189
+ title="Date & Time"
190
+ width="15%"
191
+ render={(value) => (
192
+ <DateField
193
+ style={{ verticalAlign: 'middle' }}
194
+ value={value}
195
+ format="YYYY-MM-DD hh:mm:ss"
196
+ />
197
+ )}
198
+ filterDropdown={(props) => (
199
+ <FilterDropdown
200
+ {...(props as any)}
201
+ mapValue={rangePickerFilterMapper}
202
+ clearFilters={() => {
203
+ props.setSelectedKeys([]);
204
+ props.confirm?.();
205
+ }}
206
+ >
207
+ <DatePicker.RangePicker />
208
+ </FilterDropdown>
209
+ )}
210
+ sorter
211
+ defaultFilteredValue={getDefaultFilter(
212
+ 'timestamp',
213
+ filters,
214
+ 'between',
215
+ )}
216
+ defaultSortOrder={getDefaultSortOrder('timestamp', sorters)}
217
+ />
218
+ </Table>
219
+ </Card>
217
220
  </List>
218
221
  </div>
219
222
  );
@@ -38,6 +38,8 @@ import { useTranslation } from 'react-i18next';
38
38
  import { useUserPageAccess } from 'hooks/useUserPageAccess';
39
39
  import { customRequest } from 'helpers/http';
40
40
  import { useIsAgentAllowed } from 'config/agentAllowed';
41
+ import { PageHeader } from 'components';
42
+ import { PAGE_WRAPPER_STYLE } from 'constants/layout';
41
43
 
42
44
  const COLS = 48;
43
45
  const ROW_HEIGHT = 10;
@@ -242,24 +244,11 @@ const CanvasListPage: React.FC = () => {
242
244
  }
243
245
 
244
246
  return (
245
- <div
246
- style={{
247
- background: 'var(--ant-color-bg-layout)',
248
- minHeight: 'calc(100vh - 64px)',
249
- }}
250
- >
247
+ <div style={PAGE_WRAPPER_STYLE}>
251
248
  {/* Header */}
252
- <Card style={{ marginBottom: 16 }}>
253
- <div
254
- style={{
255
- display: 'flex',
256
- justifyContent: 'space-between',
257
- alignItems: 'center',
258
- }}
259
- >
260
- <Typography.Title level={4} style={{ margin: 0 }}>
261
- {t('canvas.list.title')}
262
- </Typography.Title>
249
+ <PageHeader
250
+ title={t('canvas.list.title')}
251
+ extra={
263
252
  <Space>
264
253
  {isAgentAllowed && (
265
254
  <Button
@@ -279,8 +268,8 @@ const CanvasListPage: React.FC = () => {
279
268
  </Button>
280
269
  )}
281
270
  </Space>
282
- </div>
283
- </Card>
271
+ }
272
+ />
284
273
 
285
274
  {/* Grid */}
286
275
  <Card>
@@ -15,6 +15,8 @@ import {
15
15
  SyncOutlined,
16
16
  } from '@ant-design/icons';
17
17
  import { customRequest } from 'helpers/http';
18
+ import { PageHeader } from 'components';
19
+ import { PAGE_WRAPPER_STYLE } from 'constants/layout';
18
20
  import { DecisionModal } from './DecisionModal';
19
21
 
20
22
  interface InboxItem {
@@ -101,28 +103,19 @@ export default function ApprovalInboxPage() {
101
103
  }
102
104
 
103
105
  return (
104
- <div style={{ padding: 24, minHeight: '100%', background: '#f5f7fa' }}>
105
- <Card>
106
- <div
107
- style={{
108
- display: 'flex',
109
- justifyContent: 'space-between',
110
- alignItems: 'center',
111
- marginBottom: 20,
112
- }}
113
- >
114
- <div>
115
- <Typography.Title level={3} style={{ margin: 0 }}>
116
- Approval tasks
117
- </Typography.Title>
118
- <Typography.Text type="secondary">
119
- Requests waiting for your decision
120
- </Typography.Text>
121
- </div>
106
+ <div style={PAGE_WRAPPER_STYLE}>
107
+ <PageHeader
108
+ title="Approval tasks"
109
+ subtitle="Requests waiting for your decision"
110
+ extra={
122
111
  <Button icon={<ReloadOutlined />} loading={loading} onClick={load}>
123
112
  Refresh
124
113
  </Button>
125
- </div>
114
+ }
115
+ />
116
+
117
+ {/* Table Card */}
118
+ <Card className="card-table">
126
119
  <Table
127
120
  rowKey={(item) => String(item.id)}
128
121
  loading={loading}