@robsun/create-keystone-app 0.2.11 → 0.2.13

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.
@@ -1,52 +1,52 @@
1
- {
2
- "name": "web",
3
- "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "build:strict": "tsc -b && vite build",
10
- "lint": "eslint .",
11
- "preview": "vite preview",
12
- "lint:fix": "eslint . --fix",
13
- "format": "prettier --write \"src/**/*.{ts,tsx}\"",
14
- "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
15
- "typecheck": "tsc --noEmit",
16
- "test": "vitest run --passWithNoTests"
17
- },
18
- "dependencies": {
19
- "@ant-design/icons": "^6.1.0",
20
- "@robsun/keystone-web-core": "^0.2.1",
21
- "antd": "^6.0.1",
22
- "dayjs": "^1.11.19",
23
- "i18next": "^24.2.3",
24
- "react": "^19.2.0",
25
- "react-dom": "^19.2.0",
26
- "react-i18next": "^15.5.1",
27
- "react-router-dom": "^7.10.1"
28
- },
29
- "devDependencies": {
30
- "@eslint/js": "^9.39.1",
31
- "@tailwindcss/postcss": "^4.1.17",
32
- "@testing-library/jest-dom": "^6.6.3",
33
- "@testing-library/react": "^16.2.0",
34
- "@testing-library/user-event": "^14.6.1",
35
- "@types/node": "^24.10.1",
36
- "@types/react": "^19.2.5",
37
- "@types/react-dom": "^19.2.3",
38
- "@vitejs/plugin-react": "^5.1.1",
39
- "autoprefixer": "^10.4.22",
40
- "eslint": "^9.39.1",
41
- "eslint-plugin-react-hooks": "^7.0.1",
42
- "eslint-plugin-react-refresh": "^0.4.24",
43
- "globals": "^16.5.0",
44
- "jsdom": "^24.1.0",
45
- "postcss": "^8.5.6",
46
- "tailwindcss": "^4.1.17",
47
- "typescript": "~5.9.3",
48
- "typescript-eslint": "^8.46.4",
49
- "vite": "^7.2.4",
50
- "vitest": "^2.1.4"
51
- }
52
- }
1
+ {
2
+ "name": "web",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:strict": "tsc -b && vite build",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview",
12
+ "lint:fix": "eslint . --fix",
13
+ "format": "prettier --write \"src/**/*.{ts,tsx}\"",
14
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
15
+ "typecheck": "tsc --noEmit",
16
+ "test": "vitest run --passWithNoTests"
17
+ },
18
+ "dependencies": {
19
+ "@ant-design/icons": "^6.1.0",
20
+ "@robsun/keystone-web-core": "^0.3.0",
21
+ "antd": "^6.0.1",
22
+ "dayjs": "^1.11.19",
23
+ "i18next": "^24.2.3",
24
+ "react": "^19.2.0",
25
+ "react-dom": "^19.2.0",
26
+ "react-i18next": "^15.5.1",
27
+ "react-router-dom": "^7.10.1"
28
+ },
29
+ "devDependencies": {
30
+ "@eslint/js": "^9.39.1",
31
+ "@tailwindcss/postcss": "^4.1.17",
32
+ "@testing-library/jest-dom": "^6.6.3",
33
+ "@testing-library/react": "^16.2.0",
34
+ "@testing-library/user-event": "^14.6.1",
35
+ "@types/node": "^24.10.1",
36
+ "@types/react": "^19.2.5",
37
+ "@types/react-dom": "^19.2.3",
38
+ "@vitejs/plugin-react": "^5.1.1",
39
+ "autoprefixer": "^10.4.22",
40
+ "eslint": "^9.39.1",
41
+ "eslint-plugin-react-hooks": "^7.0.1",
42
+ "eslint-plugin-react-refresh": "^0.4.24",
43
+ "globals": "^16.5.0",
44
+ "jsdom": "^24.1.0",
45
+ "postcss": "^8.5.6",
46
+ "tailwindcss": "^4.1.17",
47
+ "typescript": "~5.9.3",
48
+ "typescript-eslint": "^8.46.4",
49
+ "vite": "^7.2.4",
50
+ "vitest": "^2.1.4"
51
+ }
52
+ }
@@ -1,24 +1,24 @@
1
- import type { KeystoneWebConfig } from '@robsun/keystone-web-core'
2
-
3
- export const appConfig: Partial<KeystoneWebConfig> = {
4
- brand: {
5
- name: 'Keystone',
6
- shortName: 'KS',
7
- appName: 'Keystone',
8
- platformName: 'Keystone Platform',
9
- },
10
- modules: {
11
- enabled: ['keystone', 'example'],
12
- },
13
- approval: {
14
- businessTypes: [{ value: 'general', label: 'General Flow' }],
15
- },
16
- ui: {
17
- i18n: {
18
- enabled: true,
19
- defaultLocale: 'zh-CN',
20
- supportedLocales: ['zh-CN', 'en-US'],
21
- },
22
- },
23
- }
24
-
1
+ import type { KeystoneWebConfig } from '@robsun/keystone-web-core'
2
+
3
+ export const appConfig: Partial<KeystoneWebConfig> = {
4
+ brand: {
5
+ name: 'Keystone',
6
+ shortName: 'KS',
7
+ appName: 'Keystone',
8
+ platformName: 'Keystone Platform',
9
+ },
10
+ modules: {
11
+ enabled: ['keystone', 'example'],
12
+ },
13
+ approval: {
14
+ businessTypes: [{ value: 'general', label: 'General Flow' }],
15
+ },
16
+ ui: {
17
+ i18n: {
18
+ enabled: true,
19
+ defaultLocale: 'zh-CN',
20
+ supportedLocales: ['zh-CN', 'en-US'],
21
+ },
22
+ },
23
+ }
24
+
@@ -1,10 +1,10 @@
1
- import { registerModule, loadModuleLocales } from '@robsun/keystone-web-core'
2
- import { exampleRoutes } from './routes'
3
-
4
- // Load module i18n translations
5
- loadModuleLocales('example', {
6
- 'zh-CN': () => import('./locales/zh-CN/example.json'),
7
- 'en-US': () => import('./locales/en-US/example.json'),
8
- })
9
-
10
- registerModule({ name: 'example', routes: exampleRoutes })
1
+ import { registerModule, loadModuleLocales } from '@robsun/keystone-web-core'
2
+ import { exampleRoutes } from './routes'
3
+
4
+ // Load module i18n translations
5
+ loadModuleLocales('example', {
6
+ 'zh-CN': () => import('./locales/zh-CN/example.json'),
7
+ 'en-US': () => import('./locales/en-US/example.json'),
8
+ })
9
+
10
+ registerModule({ name: 'example', routes: exampleRoutes })
@@ -1,237 +1,237 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react'
2
- import { App, Button, Card, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd'
3
- import type { ColumnsType } from 'antd/es/table'
4
- import { useTranslation } from 'react-i18next'
5
- import dayjs from 'dayjs'
6
- import {
7
- createExampleItem,
8
- deleteExampleItem,
9
- listExampleItems,
10
- updateExampleItem,
11
- } from '../services/exampleItems'
12
- import type { ExampleItem, ExampleItemStatus } from '../types'
13
-
14
- type ExampleItemFormValues = {
15
- title: string
16
- description?: string
17
- status: ExampleItemStatus
18
- }
19
-
20
- const statusColors: Record<ExampleItemStatus, string> = {
21
- active: 'success',
22
- inactive: 'default',
23
- }
24
-
25
- export function ExampleItemsPage() {
26
- const { t } = useTranslation('example')
27
- const { t: tc } = useTranslation('common')
28
- const { message } = App.useApp()
29
- const [items, setItems] = useState<ExampleItem[]>([])
30
- const [loading, setLoading] = useState(false)
31
- const [modalOpen, setModalOpen] = useState(false)
32
- const [saving, setSaving] = useState(false)
33
- const [editingItem, setEditingItem] = useState<ExampleItem | null>(null)
34
- const [form] = Form.useForm<ExampleItemFormValues>()
35
-
36
- const statusOptions = useMemo(
37
- () => [
38
- { value: 'active', label: t('status.active') },
39
- { value: 'inactive', label: t('status.inactive') },
40
- ],
41
- [t]
42
- )
43
-
44
- const fetchItems = useCallback(async () => {
45
- setLoading(true)
46
- try {
47
- const data = await listExampleItems()
48
- setItems(data)
49
- } catch (err) {
50
- const detail = err instanceof Error ? err.message : t('messages.loadFailed')
51
- message.error(detail)
52
- } finally {
53
- setLoading(false)
54
- }
55
- }, [message, t])
56
-
57
- useEffect(() => {
58
- void fetchItems()
59
- }, [fetchItems])
60
-
61
- const openCreate = useCallback(() => {
62
- setEditingItem(null)
63
- setModalOpen(true)
64
- }, [])
65
-
66
- const openEdit = useCallback(
67
- (item: ExampleItem) => {
68
- setEditingItem(item)
69
- setModalOpen(true)
70
- },
71
- []
72
- )
73
-
74
- const closeModal = useCallback(() => {
75
- setModalOpen(false)
76
- setEditingItem(null)
77
- }, [])
78
-
79
- useEffect(() => {
80
- if (!modalOpen) {
81
- return
82
- }
83
- form.resetFields()
84
- if (editingItem) {
85
- form.setFieldsValue({
86
- title: editingItem.title,
87
- description: editingItem.description,
88
- status: editingItem.status,
89
- })
90
- } else {
91
- form.setFieldsValue({ status: 'active' })
92
- }
93
- }, [editingItem, form, modalOpen])
94
-
95
- const handleSubmit = useCallback(async () => {
96
- let values: ExampleItemFormValues
97
- try {
98
- values = await form.validateFields()
99
- } catch {
100
- return
101
- }
102
-
103
- const payload = {
104
- title: values.title.trim(),
105
- description: values.description?.trim() ?? '',
106
- status: values.status,
107
- }
108
-
109
- setSaving(true)
110
- try {
111
- if (editingItem) {
112
- await updateExampleItem(editingItem.id, payload)
113
- message.success(t('messages.updateSuccess'))
114
- } else {
115
- await createExampleItem(payload)
116
- message.success(t('messages.createSuccess'))
117
- }
118
- closeModal()
119
- await fetchItems()
120
- } catch (err) {
121
- const detail = err instanceof Error ? err.message : tc('messages.operationFailed')
122
- message.error(detail)
123
- } finally {
124
- setSaving(false)
125
- }
126
- }, [closeModal, editingItem, fetchItems, form, message, t, tc])
127
-
128
- const handleDelete = useCallback(
129
- async (id: number) => {
130
- try {
131
- await deleteExampleItem(id)
132
- await fetchItems()
133
- message.success(t('messages.deleteSuccess'))
134
- } catch (err) {
135
- const detail = err instanceof Error ? err.message : tc('messages.operationFailed')
136
- message.error(detail)
137
- }
138
- },
139
- [fetchItems, message, t, tc]
140
- )
141
-
142
- const columns: ColumnsType<ExampleItem> = useMemo(
143
- () => [
144
- { title: t('table.name'), dataIndex: 'title', key: 'title' },
145
- {
146
- title: t('table.description'),
147
- dataIndex: 'description',
148
- key: 'description',
149
- render: (value: string) =>
150
- value ? <Typography.Text type="secondary">{value}</Typography.Text> : '-',
151
- },
152
- {
153
- title: t('table.status'),
154
- dataIndex: 'status',
155
- key: 'status',
156
- render: (value: ExampleItemStatus) => (
157
- <Tag color={statusColors[value]}>{t(`status.${value}`)}</Tag>
158
- ),
159
- },
160
- {
161
- title: tc('table.updatedAt'),
162
- dataIndex: 'updated_at',
163
- key: 'updated_at',
164
- render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'),
165
- },
166
- {
167
- title: tc('table.actions'),
168
- key: 'actions',
169
- render: (_, record) => (
170
- <Space>
171
- <Button type="link" onClick={() => openEdit(record)}>
172
- {tc('actions.edit')}
173
- </Button>
174
- <Popconfirm title={tc('confirm.deleteContent')} onConfirm={() => handleDelete(record.id)}>
175
- <Button type="link" danger>
176
- {tc('actions.delete')}
177
- </Button>
178
- </Popconfirm>
179
- </Space>
180
- ),
181
- },
182
- ],
183
- [handleDelete, openEdit, t, tc]
184
- )
185
-
186
- return (
187
- <Card
188
- title={t('page.title')}
189
- extra={
190
- <Space>
191
- <Button onClick={fetchItems} loading={loading}>
192
- {tc('actions.refresh')}
193
- </Button>
194
- <Button type="primary" onClick={openCreate}>
195
- {t('page.createButton')}
196
- </Button>
197
- </Space>
198
- }
199
- >
200
- <Space direction="vertical" size="middle" style={{ width: '100%' }}>
201
- <Table<ExampleItem>
202
- rowKey="id"
203
- loading={loading}
204
- columns={columns}
205
- dataSource={items}
206
- pagination={false}
207
- />
208
- </Space>
209
-
210
- <Modal
211
- title={editingItem ? tc('actions.edit') : tc('actions.create')}
212
- open={modalOpen}
213
- onCancel={closeModal}
214
- onOk={handleSubmit}
215
- confirmLoading={saving}
216
- okText={editingItem ? tc('actions.save') : tc('actions.create')}
217
- destroyOnHidden
218
- >
219
- <Form form={form} layout="vertical" initialValues={{ status: 'active' }}>
220
- <Form.Item
221
- label={t('form.nameLabel')}
222
- name="title"
223
- rules={[{ required: true, whitespace: true, message: tc('form.required') }]}
224
- >
225
- <Input placeholder={t('form.namePlaceholder')} allowClear />
226
- </Form.Item>
227
- <Form.Item label={t('form.descriptionLabel')} name="description">
228
- <Input.TextArea rows={3} placeholder={t('form.descriptionPlaceholder')} />
229
- </Form.Item>
230
- <Form.Item label={t('table.status')} name="status" rules={[{ required: true, message: tc('form.required') }]}>
231
- <Select options={statusOptions} />
232
- </Form.Item>
233
- </Form>
234
- </Modal>
235
- </Card>
236
- )
237
- }
1
+ import { useCallback, useEffect, useMemo, useState } from 'react'
2
+ import { App, Button, Card, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd'
3
+ import type { ColumnsType } from 'antd/es/table'
4
+ import { useTranslation } from 'react-i18next'
5
+ import dayjs from 'dayjs'
6
+ import {
7
+ createExampleItem,
8
+ deleteExampleItem,
9
+ listExampleItems,
10
+ updateExampleItem,
11
+ } from '../services/exampleItems'
12
+ import type { ExampleItem, ExampleItemStatus } from '../types'
13
+
14
+ type ExampleItemFormValues = {
15
+ title: string
16
+ description?: string
17
+ status: ExampleItemStatus
18
+ }
19
+
20
+ const statusColors: Record<ExampleItemStatus, string> = {
21
+ active: 'success',
22
+ inactive: 'default',
23
+ }
24
+
25
+ export function ExampleItemsPage() {
26
+ const { t } = useTranslation('example')
27
+ const { t: tc } = useTranslation('common')
28
+ const { message } = App.useApp()
29
+ const [items, setItems] = useState<ExampleItem[]>([])
30
+ const [loading, setLoading] = useState(false)
31
+ const [modalOpen, setModalOpen] = useState(false)
32
+ const [saving, setSaving] = useState(false)
33
+ const [editingItem, setEditingItem] = useState<ExampleItem | null>(null)
34
+ const [form] = Form.useForm<ExampleItemFormValues>()
35
+
36
+ const statusOptions = useMemo(
37
+ () => [
38
+ { value: 'active', label: t('status.active') },
39
+ { value: 'inactive', label: t('status.inactive') },
40
+ ],
41
+ [t]
42
+ )
43
+
44
+ const fetchItems = useCallback(async () => {
45
+ setLoading(true)
46
+ try {
47
+ const data = await listExampleItems()
48
+ setItems(data)
49
+ } catch (err) {
50
+ const detail = err instanceof Error ? err.message : t('messages.loadFailed')
51
+ message.error(detail)
52
+ } finally {
53
+ setLoading(false)
54
+ }
55
+ }, [message, t])
56
+
57
+ useEffect(() => {
58
+ void fetchItems()
59
+ }, [fetchItems])
60
+
61
+ const openCreate = useCallback(() => {
62
+ setEditingItem(null)
63
+ setModalOpen(true)
64
+ }, [])
65
+
66
+ const openEdit = useCallback(
67
+ (item: ExampleItem) => {
68
+ setEditingItem(item)
69
+ setModalOpen(true)
70
+ },
71
+ []
72
+ )
73
+
74
+ const closeModal = useCallback(() => {
75
+ setModalOpen(false)
76
+ setEditingItem(null)
77
+ }, [])
78
+
79
+ useEffect(() => {
80
+ if (!modalOpen) {
81
+ return
82
+ }
83
+ form.resetFields()
84
+ if (editingItem) {
85
+ form.setFieldsValue({
86
+ title: editingItem.title,
87
+ description: editingItem.description,
88
+ status: editingItem.status,
89
+ })
90
+ } else {
91
+ form.setFieldsValue({ status: 'active' })
92
+ }
93
+ }, [editingItem, form, modalOpen])
94
+
95
+ const handleSubmit = useCallback(async () => {
96
+ let values: ExampleItemFormValues
97
+ try {
98
+ values = await form.validateFields()
99
+ } catch {
100
+ return
101
+ }
102
+
103
+ const payload = {
104
+ title: values.title.trim(),
105
+ description: values.description?.trim() ?? '',
106
+ status: values.status,
107
+ }
108
+
109
+ setSaving(true)
110
+ try {
111
+ if (editingItem) {
112
+ await updateExampleItem(editingItem.id, payload)
113
+ message.success(t('messages.updateSuccess'))
114
+ } else {
115
+ await createExampleItem(payload)
116
+ message.success(t('messages.createSuccess'))
117
+ }
118
+ closeModal()
119
+ await fetchItems()
120
+ } catch (err) {
121
+ const detail = err instanceof Error ? err.message : tc('messages.operationFailed')
122
+ message.error(detail)
123
+ } finally {
124
+ setSaving(false)
125
+ }
126
+ }, [closeModal, editingItem, fetchItems, form, message, t, tc])
127
+
128
+ const handleDelete = useCallback(
129
+ async (id: number) => {
130
+ try {
131
+ await deleteExampleItem(id)
132
+ await fetchItems()
133
+ message.success(t('messages.deleteSuccess'))
134
+ } catch (err) {
135
+ const detail = err instanceof Error ? err.message : tc('messages.operationFailed')
136
+ message.error(detail)
137
+ }
138
+ },
139
+ [fetchItems, message, t, tc]
140
+ )
141
+
142
+ const columns: ColumnsType<ExampleItem> = useMemo(
143
+ () => [
144
+ { title: t('table.name'), dataIndex: 'title', key: 'title' },
145
+ {
146
+ title: t('table.description'),
147
+ dataIndex: 'description',
148
+ key: 'description',
149
+ render: (value: string) =>
150
+ value ? <Typography.Text type="secondary">{value}</Typography.Text> : '-',
151
+ },
152
+ {
153
+ title: t('table.status'),
154
+ dataIndex: 'status',
155
+ key: 'status',
156
+ render: (value: ExampleItemStatus) => (
157
+ <Tag color={statusColors[value]}>{t(`status.${value}`)}</Tag>
158
+ ),
159
+ },
160
+ {
161
+ title: tc('table.updatedAt'),
162
+ dataIndex: 'updated_at',
163
+ key: 'updated_at',
164
+ render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'),
165
+ },
166
+ {
167
+ title: tc('table.actions'),
168
+ key: 'actions',
169
+ render: (_, record) => (
170
+ <Space>
171
+ <Button type="link" onClick={() => openEdit(record)}>
172
+ {tc('actions.edit')}
173
+ </Button>
174
+ <Popconfirm title={tc('confirm.deleteContent')} onConfirm={() => handleDelete(record.id)}>
175
+ <Button type="link" danger>
176
+ {tc('actions.delete')}
177
+ </Button>
178
+ </Popconfirm>
179
+ </Space>
180
+ ),
181
+ },
182
+ ],
183
+ [handleDelete, openEdit, t, tc]
184
+ )
185
+
186
+ return (
187
+ <Card
188
+ title={t('page.title')}
189
+ extra={
190
+ <Space>
191
+ <Button onClick={fetchItems} loading={loading}>
192
+ {tc('actions.refresh')}
193
+ </Button>
194
+ <Button type="primary" onClick={openCreate}>
195
+ {t('page.createButton')}
196
+ </Button>
197
+ </Space>
198
+ }
199
+ >
200
+ <Space direction="vertical" size="middle" style={{ width: '100%' }}>
201
+ <Table<ExampleItem>
202
+ rowKey="id"
203
+ loading={loading}
204
+ columns={columns}
205
+ dataSource={items}
206
+ pagination={false}
207
+ />
208
+ </Space>
209
+
210
+ <Modal
211
+ title={editingItem ? tc('actions.edit') : tc('actions.create')}
212
+ open={modalOpen}
213
+ onCancel={closeModal}
214
+ onOk={handleSubmit}
215
+ confirmLoading={saving}
216
+ okText={editingItem ? tc('actions.save') : tc('actions.create')}
217
+ destroyOnHidden
218
+ >
219
+ <Form form={form} layout="vertical" initialValues={{ status: 'active' }}>
220
+ <Form.Item
221
+ label={t('form.nameLabel')}
222
+ name="title"
223
+ rules={[{ required: true, whitespace: true, message: tc('form.required') }]}
224
+ >
225
+ <Input placeholder={t('form.namePlaceholder')} allowClear />
226
+ </Form.Item>
227
+ <Form.Item label={t('form.descriptionLabel')} name="description">
228
+ <Input.TextArea rows={3} placeholder={t('form.descriptionPlaceholder')} />
229
+ </Form.Item>
230
+ <Form.Item label={t('table.status')} name="status" rules={[{ required: true, message: tc('form.required') }]}>
231
+ <Select options={statusOptions} />
232
+ </Form.Item>
233
+ </Form>
234
+ </Modal>
235
+ </Card>
236
+ )
237
+ }