create-fullstack-scaffold 0.4.11 → 0.4.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fullstack-scaffold",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "create-fullstack-scaffold": "dist/cli/index.js"
@@ -80,7 +80,10 @@ export const App: React.FC<{ basePath?: string }> = ({ basePath = '/admin' }) =>
80
80
  <Route path="/content" element={<ContentPage />} />
81
81
  <Route path="/system/settings" element={<SettingsPage />} />
82
82
  <Route path="/system/logs" element={<SystemLogsPage />} />
83
- <Route path="/system/monitor" element={<ComingSoonPage title="System Monitor" />} />
83
+ <Route
84
+ path="/system/monitor"
85
+ element={<ComingSoonPage title="System Monitor" />}
86
+ />
84
87
  <Route path="/system/permissions" element={<PermissionsPage />} />
85
88
  <Route path="/system/roles" element={<RolesPage />} />
86
89
  </Routes>
@@ -17,11 +17,7 @@ const typeConfig: Record<
17
17
  NotificationType,
18
18
  {
19
19
  colorToken: 'colorInfo' | 'colorWarning' | 'colorError' | 'colorSuccess'
20
- bgToken:
21
- | 'colorInfoBg'
22
- | 'colorWarningBg'
23
- | 'colorErrorBg'
24
- | 'colorSuccessBg'
20
+ bgToken: 'colorInfoBg' | 'colorWarningBg' | 'colorErrorBg' | 'colorSuccessBg'
25
21
  icon: React.ReactNode
26
22
  }
27
23
  > = {
@@ -50,7 +46,7 @@ const typeConfig: Record<
50
46
  function formatTime(
51
47
  dateString: string,
52
48
  locale: string,
53
- t: (key: string, options?: Record<string, unknown>) => string,
49
+ t: (key: string, options?: Record<string, unknown>) => string
54
50
  ): string {
55
51
  const date = new Date(dateString)
56
52
  const now = new Date()
@@ -90,15 +86,12 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
90
86
  className="p-4 cursor-pointer transition-colors"
91
87
  style={{
92
88
  borderBottom: `1px solid ${token.colorBorderSecondary}`,
93
- backgroundColor: !notif.read ? token.colorInfoBg : undefined,
89
+ backgroundColor: !notif.read ? (token.colorInfoBg ?? 'transparent') : undefined,
94
90
  }}
95
91
  onClick={() => !notif.read && onMarkAsRead(notif.id)}
96
- onMouseEnter={e =>
97
- (e.currentTarget.style.backgroundColor = token.colorBgTextHover)
98
- }
92
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = token.colorBgTextHover ?? 'transparent')}
99
93
  onMouseLeave={e =>
100
- (e.currentTarget.style.backgroundColor =
101
- !notif.read ? token.colorInfoBg : undefined)
94
+ (e.currentTarget.style.backgroundColor = !notif.read ? (token.colorInfoBg ?? 'transparent') : 'transparent')
102
95
  }
103
96
  >
104
97
  <div className="flex items-start gap-3">
@@ -110,10 +103,7 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
110
103
  </div>
111
104
  <div className="flex-1 min-w-0">
112
105
  <div className="flex items-center gap-2">
113
- <span
114
- className="font-medium truncate"
115
- style={{ color: token.colorText }}
116
- >
106
+ <span className="font-medium truncate" style={{ color: token.colorText }}>
117
107
  {notif.title}
118
108
  </span>
119
109
  {!notif.read && (
@@ -123,16 +113,10 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
123
113
  />
124
114
  )}
125
115
  </div>
126
- <p
127
- className="text-sm mt-1 line-clamp-2"
128
- style={{ color: token.colorTextSecondary }}
129
- >
116
+ <p className="text-sm mt-1 line-clamp-2" style={{ color: token.colorTextSecondary }}>
130
117
  {notif.message}
131
118
  </p>
132
- <span
133
- className="text-xs mt-1 block"
134
- style={{ color: token.colorTextTertiary }}
135
- >
119
+ <span className="text-xs mt-1 block" style={{ color: token.colorTextTertiary }}>
136
120
  {formatTime(notif.createdAt, currentLanguage, t)}
137
121
  </span>
138
122
  </div>
@@ -149,10 +133,7 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
149
133
  key: 'all',
150
134
  label: `${t('notification.all')} (${notifications.length})`,
151
135
  children: (
152
- <div
153
- className="overflow-y-auto"
154
- style={{ maxHeight: 'calc(100vh - 200px)' }}
155
- >
136
+ <div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 200px)' }}>
156
137
  {loading ? (
157
138
  <div className="flex items-center justify-center py-12">
158
139
  <Spin />
@@ -173,10 +154,7 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
173
154
  key: 'unread',
174
155
  label: `${t('notification.unread')} (${unreadNotifications.length})`,
175
156
  children: (
176
- <div
177
- className="overflow-y-auto"
178
- style={{ maxHeight: 'calc(100vh - 200px)' }}
179
- >
157
+ <div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 200px)' }}>
180
158
  {unreadNotifications.length === 0 ? (
181
159
  <Empty
182
160
  image={Empty.PRESENTED_IMAGE_SIMPLE}
@@ -193,10 +171,7 @@ export const NotificationDrawer: React.FC<NotificationDrawerProps> = ({
193
171
  key: 'read',
194
172
  label: `${t('notification.read')} (${readNotifications.length})`,
195
173
  children: (
196
- <div
197
- className="overflow-y-auto"
198
- style={{ maxHeight: 'calc(100vh - 200px)' }}
199
- >
174
+ <div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 200px)' }}>
200
175
  {readNotifications.length === 0 ? (
201
176
  <Empty
202
177
  image={Empty.PRESENTED_IMAGE_SIMPLE}
@@ -255,12 +230,8 @@ export const NotificationBell: React.FC<{
255
230
  className="p-2 rounded-lg transition-colors relative"
256
231
  style={{ color: token.colorTextSecondary }}
257
232
  onClick={onClick}
258
- onMouseEnter={e =>
259
- (e.currentTarget.style.backgroundColor = token.colorBgTextHover)
260
- }
261
- onMouseLeave={e =>
262
- (e.currentTarget.style.backgroundColor = 'transparent')
263
- }
233
+ onMouseEnter={e => (e.currentTarget.style.backgroundColor = token.colorBgTextHover)}
234
+ onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
264
235
  >
265
236
  <Bell className="w-5 h-5" />
266
237
  {unreadCount > 0 && (
@@ -13,10 +13,10 @@ export const ThemeToggle: React.FC = () => {
13
13
  onClick={toggleTheme}
14
14
  className="p-2 rounded-lg transition-colors cursor-pointer border-none bg-transparent"
15
15
  style={{ color: token.colorText }}
16
- onMouseEnter={(e) => {
16
+ onMouseEnter={e => {
17
17
  e.currentTarget.style.backgroundColor = token.colorFillQuaternary
18
18
  }}
19
- onMouseLeave={(e) => {
19
+ onMouseLeave={e => {
20
20
  e.currentTarget.style.backgroundColor = 'transparent'
21
21
  }}
22
22
  title={mode === 'light' ? t('theme.toggleDark') : t('theme.toggleLight')}
@@ -37,11 +37,14 @@ export const Header: React.FC<HeaderProps> = ({ collapsed, onToggle }) => {
37
37
  navigate('/login')
38
38
  }
39
39
 
40
- const ROLE_OPTIONS = useMemo(() => [
41
- { label: t('header.superAdmin'), value: 'super_admin' },
42
- { label: t('header.moderator'), value: 'moderator' },
43
- { label: t('header.operator'), value: 'operator' },
44
- ], [t])
40
+ const ROLE_OPTIONS = useMemo(
41
+ () => [
42
+ { label: t('header.superAdmin'), value: 'super_admin' },
43
+ { label: t('header.moderator'), value: 'moderator' },
44
+ { label: t('header.operator'), value: 'operator' },
45
+ ],
46
+ [t]
47
+ )
45
48
 
46
49
  const userMenuItems: MenuProps['items'] = [
47
50
  {
@@ -83,10 +86,10 @@ export const Header: React.FC<HeaderProps> = ({ collapsed, onToggle }) => {
83
86
  onClick={onToggle}
84
87
  className="p-2 rounded-lg transition-colors cursor-pointer border-none bg-transparent"
85
88
  style={{ color: token.colorText }}
86
- onMouseEnter={(e) => {
89
+ onMouseEnter={e => {
87
90
  e.currentTarget.style.backgroundColor = token.colorFillQuaternary
88
91
  }}
89
- onMouseLeave={(e) => {
92
+ onMouseLeave={e => {
90
93
  e.currentTarget.style.backgroundColor = 'transparent'
91
94
  }}
92
95
  data-testid="toggle-sidebar-button"
@@ -117,10 +120,10 @@ export const Header: React.FC<HeaderProps> = ({ collapsed, onToggle }) => {
117
120
  <button
118
121
  className="flex items-center gap-2 p-1.5 rounded-lg transition-colors cursor-pointer border-none bg-transparent"
119
122
  style={{ color: token.colorText }}
120
- onMouseEnter={(e) => {
123
+ onMouseEnter={e => {
121
124
  e.currentTarget.style.backgroundColor = token.colorFillQuaternary
122
125
  }}
123
- onMouseLeave={(e) => {
126
+ onMouseLeave={e => {
124
127
  e.currentTarget.style.backgroundColor = 'transparent'
125
128
  }}
126
129
  >
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react'
1
+ import { useState, useEffect, useCallback } from 'react'
2
2
  import { Table, Card, Tag, Button, Space, Modal, Form, Input, Select, message } from 'antd'
3
3
  import { Plus, Edit, Delete } from 'lucide-react'
4
4
  import { PermissionGuard } from '../components/PermissionGuard'
@@ -41,11 +41,7 @@ export const ContentPage: React.FC = () => {
41
41
  return map[status] || status
42
42
  }
43
43
 
44
- useEffect(() => {
45
- fetchContents()
46
- }, [])
47
-
48
- const fetchContents = async () => {
44
+ const fetchContents = useCallback(async () => {
49
45
  setLoading(true)
50
46
  try {
51
47
  const response = await apiClient.api.contents.$get({ query: {} })
@@ -60,7 +56,11 @@ export const ContentPage: React.FC = () => {
60
56
  } finally {
61
57
  setLoading(false)
62
58
  }
63
- }
59
+ }, [t])
60
+
61
+ useEffect(() => {
62
+ fetchContents()
63
+ }, [fetchContents])
64
64
 
65
65
  const handleCreate = () => {
66
66
  setEditingContent(null)
@@ -142,9 +142,7 @@ export const ContentPage: React.FC = () => {
142
142
  title: t('common.status'),
143
143
  dataIndex: 'status',
144
144
  key: 'status',
145
- render: (status: string) => (
146
- <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>
147
- ),
145
+ render: (status: string) => <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>,
148
146
  },
149
147
  {
150
148
  title: t('content.author'),
@@ -41,10 +41,30 @@ export const DashboardPage: React.FC = () => {
41
41
  }
42
42
 
43
43
  const statCards = [
44
- { title: t('dashboard.totalTodos'), value: stats?.totalTodos || 0, icon: CheckCircle, color: 'bg-blue-500' },
45
- { title: t('dashboard.pending'), value: stats?.pendingTodos || 0, icon: Clock, color: 'bg-yellow-500' },
46
- { title: t('dashboard.completed'), value: stats?.completedTodos || 0, icon: TrendingUp, color: 'bg-green-500' },
47
- { title: t('dashboard.lastUpdated'), value: stats?.lastUpdated || '-', icon: Activity, color: 'bg-purple-500' },
44
+ {
45
+ title: t('dashboard.totalTodos'),
46
+ value: stats?.totalTodos || 0,
47
+ icon: CheckCircle,
48
+ color: 'bg-blue-500',
49
+ },
50
+ {
51
+ title: t('dashboard.pending'),
52
+ value: stats?.pendingTodos || 0,
53
+ icon: Clock,
54
+ color: 'bg-yellow-500',
55
+ },
56
+ {
57
+ title: t('dashboard.completed'),
58
+ value: stats?.completedTodos || 0,
59
+ icon: TrendingUp,
60
+ color: 'bg-green-500',
61
+ },
62
+ {
63
+ title: t('dashboard.lastUpdated'),
64
+ value: stats?.lastUpdated || '-',
65
+ icon: Activity,
66
+ color: 'bg-purple-500',
67
+ },
48
68
  ]
49
69
 
50
70
  const notificationTypeOptions: { value: NotificationType; label: string }[] = [
@@ -75,10 +95,7 @@ export const DashboardPage: React.FC = () => {
75
95
 
76
96
  return (
77
97
  <div>
78
- <h1
79
- className="text-2xl font-bold mb-4"
80
- style={{ color: token.colorText }}
81
- >
98
+ <h1 className="text-2xl font-bold mb-4" style={{ color: token.colorText }}>
82
99
  {t('dashboard.title')}
83
100
  </h1>
84
101
 
@@ -95,20 +112,16 @@ export const DashboardPage: React.FC = () => {
95
112
  }}
96
113
  >
97
114
  <div className="flex items-center justify-between mb-2">
98
- <div className={`p-3 rounded-md ${card.color} w-10 h-10 flex items-center justify-center`}>
115
+ <div
116
+ className={`p-3 rounded-md ${card.color} w-10 h-10 flex items-center justify-center`}
117
+ >
99
118
  <Icon className="w-6 h-6 text-white" />
100
119
  </div>
101
120
  </div>
102
- <h3
103
- className="text-sm font-medium"
104
- style={{ color: token.colorTextSecondary }}
105
- >
121
+ <h3 className="text-sm font-medium" style={{ color: token.colorTextSecondary }}>
106
122
  {card.title}
107
123
  </h3>
108
- <p
109
- className="text-2xl font-bold mt-1"
110
- style={{ color: token.colorText }}
111
- >
124
+ <p className="text-2xl font-bold mt-1" style={{ color: token.colorText }}>
112
125
  {card.value}
113
126
  </p>
114
127
  </div>
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react'
1
+ import { useState, useEffect, useCallback } from 'react'
2
2
  import { Table, Card, Tag, Button, Space, Modal, message, Descriptions } from 'antd'
3
3
  import { Eye, AlertTriangle, CheckCircle } from 'lucide-react'
4
4
  import { PermissionGuard } from '../components/PermissionGuard'
@@ -42,11 +42,7 @@ export const DisputesPage: React.FC = () => {
42
42
  return map[status] || status
43
43
  }
44
44
 
45
- useEffect(() => {
46
- fetchDisputes()
47
- }, [])
48
-
49
- const fetchDisputes = async () => {
45
+ const fetchDisputes = useCallback(async () => {
50
46
  setLoading(true)
51
47
  try {
52
48
  const response = await apiClient.api.disputes.$get({ query: {} })
@@ -61,7 +57,11 @@ export const DisputesPage: React.FC = () => {
61
57
  } finally {
62
58
  setLoading(false)
63
59
  }
64
- }
60
+ }, [t])
61
+
62
+ useEffect(() => {
63
+ fetchDisputes()
64
+ }, [fetchDisputes])
65
65
 
66
66
  const handleResolve = (dispute: Dispute) => {
67
67
  Modal.confirm({
@@ -138,9 +138,7 @@ export const DisputesPage: React.FC = () => {
138
138
  title: t('common.status'),
139
139
  dataIndex: 'status',
140
140
  key: 'status',
141
- render: (status: string) => (
142
- <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>
143
- ),
141
+ render: (status: string) => <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>,
144
142
  },
145
143
  {
146
144
  title: t('common.createdAt'),
@@ -244,11 +242,15 @@ export const DisputesPage: React.FC = () => {
244
242
  <Descriptions.Item label={t('disputes.disputeNo')}>
245
243
  <code>{selectedDispute.disputeNo}</code>
246
244
  </Descriptions.Item>
247
- <Descriptions.Item label={t('disputes.orderNo')}>{selectedDispute.orderNo}</Descriptions.Item>
245
+ <Descriptions.Item label={t('disputes.orderNo')}>
246
+ {selectedDispute.orderNo}
247
+ </Descriptions.Item>
248
248
  <Descriptions.Item label={t('disputes.customer')}>
249
249
  {selectedDispute.customerName} ({selectedDispute.customerEmail})
250
250
  </Descriptions.Item>
251
- <Descriptions.Item label={t('disputes.type')}>{getTypeLabel(selectedDispute.type)}</Descriptions.Item>
251
+ <Descriptions.Item label={t('disputes.type')}>
252
+ {getTypeLabel(selectedDispute.type)}
253
+ </Descriptions.Item>
252
254
  <Descriptions.Item label={t('disputes.amount')}>
253
255
  <span className="text-lg font-bold text-red-600">
254
256
  ¥{selectedDispute.amount.toFixed(2)}
@@ -259,9 +261,13 @@ export const DisputesPage: React.FC = () => {
259
261
  {getStatusLabel(selectedDispute.status)}
260
262
  </Tag>
261
263
  </Descriptions.Item>
262
- <Descriptions.Item label={t('tickets.description')}>{selectedDispute.description}</Descriptions.Item>
264
+ <Descriptions.Item label={t('tickets.description')}>
265
+ {selectedDispute.description}
266
+ </Descriptions.Item>
263
267
  {selectedDispute.resolution && (
264
- <Descriptions.Item label={t('disputes.resolution')}>{selectedDispute.resolution}</Descriptions.Item>
268
+ <Descriptions.Item label={t('disputes.resolution')}>
269
+ {selectedDispute.resolution}
270
+ </Descriptions.Item>
265
271
  )}
266
272
  </Descriptions>
267
273
  )}
@@ -68,8 +68,16 @@ export const LoginPage: React.FC = () => {
68
68
  <div className="min-h-screen flex items-center justify-center bg-gray-100">
69
69
  <Card className="w-full max-w-md" title={t('login.title')}>
70
70
  <Spin spinning={loading}>
71
- <Form form={form} onFinish={handleSubmit} layout="vertical" data-testid="admin-login-form">
72
- <Form.Item name="username" rules={[{ required: true, message: t('login.usernameRequired') }]}>
71
+ <Form
72
+ form={form}
73
+ onFinish={handleSubmit}
74
+ layout="vertical"
75
+ data-testid="admin-login-form"
76
+ >
77
+ <Form.Item
78
+ name="username"
79
+ rules={[{ required: true, message: t('login.usernameRequired') }]}
80
+ >
73
81
  <Input
74
82
  prefix={<UserOutlined />}
75
83
  placeholder={t('login.username')}
@@ -77,7 +85,10 @@ export const LoginPage: React.FC = () => {
77
85
  data-testid="admin-login-username"
78
86
  />
79
87
  </Form.Item>
80
- <Form.Item name="password" rules={[{ required: true, message: t('login.passwordRequired') }]}>
88
+ <Form.Item
89
+ name="password"
90
+ rules={[{ required: true, message: t('login.passwordRequired') }]}
91
+ >
81
92
  <Input.Password
82
93
  prefix={<LockOutlined />}
83
94
  placeholder={t('login.password')}
@@ -135,9 +135,7 @@ export const OrdersPage: React.FC = () => {
135
135
  title: t('orders.status'),
136
136
  dataIndex: 'status',
137
137
  key: 'status',
138
- render: (status: string) => (
139
- <Tag color={STATUS_COLORS[status]}>{getLabel(status)}</Tag>
140
- ),
138
+ render: (status: string) => <Tag color={STATUS_COLORS[status]}>{getLabel(status)}</Tag>,
141
139
  },
142
140
  {
143
141
  title: t('common.createdAt'),
@@ -282,9 +280,15 @@ export const OrdersPage: React.FC = () => {
282
280
  <Descriptions.Item label={t('orders.orderNo')}>
283
281
  <code>{selectedOrder.orderNo}</code>
284
282
  </Descriptions.Item>
285
- <Descriptions.Item label={t('orders.customerName')}>{selectedOrder.customerName}</Descriptions.Item>
286
- <Descriptions.Item label={t('orders.customerEmail')}>{selectedOrder.customerEmail}</Descriptions.Item>
287
- <Descriptions.Item label={t('orders.productName')}>{selectedOrder.productName}</Descriptions.Item>
283
+ <Descriptions.Item label={t('orders.customerName')}>
284
+ {selectedOrder.customerName}
285
+ </Descriptions.Item>
286
+ <Descriptions.Item label={t('orders.customerEmail')}>
287
+ {selectedOrder.customerEmail}
288
+ </Descriptions.Item>
289
+ <Descriptions.Item label={t('orders.productName')}>
290
+ {selectedOrder.productName}
291
+ </Descriptions.Item>
288
292
  <Descriptions.Item label={t('orders.orderAmount')}>
289
293
  <span className="text-lg font-bold text-green-600">
290
294
  ¥{selectedOrder.amount.toFixed(2)}
@@ -64,9 +64,7 @@ export const PermissionsPage: React.FC = () => {
64
64
  <div className="grid grid-cols-3 gap-4">
65
65
  {roles.map(role => (
66
66
  <Card key={role.role} size="small">
67
- <div className="text-lg font-semibold mb-2">
68
- {roleLabels[role.role] || role.role}
69
- </div>
67
+ <div className="text-lg font-semibold mb-2">{roleLabels[role.role] || role.role}</div>
70
68
  <div className="text-sm text-gray-500">
71
69
  {t('permissions.permissionCount', { count: role.permissions.length })}
72
70
  </div>
@@ -59,11 +59,13 @@ export const RegisterPage: React.FC = () => {
59
59
  </Form.Item>
60
60
  <Form.Item
61
61
  name="password"
62
- rules={[
63
- { required: true, min: 6, message: t('register.passwordMin') },
64
- ]}
62
+ rules={[{ required: true, min: 6, message: t('register.passwordMin') }]}
65
63
  >
66
- <Input.Password prefix={<LockOutlined />} placeholder={t('register.password')} size="large" />
64
+ <Input.Password
65
+ prefix={<LockOutlined />}
66
+ placeholder={t('register.password')}
67
+ size="large"
68
+ />
67
69
  </Form.Item>
68
70
  <Form.Item
69
71
  name="confirmPassword"
@@ -175,7 +175,9 @@ export const RolesPage: React.FC = () => {
175
175
  dataIndex: 'isSystem',
176
176
  key: 'isSystem',
177
177
  render: (isSystem: boolean) => (
178
- <Tag color={isSystem ? 'blue' : 'default'}>{isSystem ? t('common.yes') : t('common.no')}</Tag>
178
+ <Tag color={isSystem ? 'blue' : 'default'}>
179
+ {isSystem ? t('common.yes') : t('common.no')}
180
+ </Tag>
179
181
  ),
180
182
  },
181
183
  {
@@ -183,7 +185,9 @@ export const RolesPage: React.FC = () => {
183
185
  dataIndex: 'isActive',
184
186
  key: 'isActive',
185
187
  render: (isActive: boolean) => (
186
- <Tag color={isActive ? 'green' : 'red'}>{isActive ? t('common.enabled') : t('common.disabled')}</Tag>
188
+ <Tag color={isActive ? 'green' : 'red'}>
189
+ {isActive ? t('common.enabled') : t('common.disabled')}
190
+ </Tag>
187
191
  ),
188
192
  },
189
193
  {
@@ -270,7 +274,10 @@ export const RolesPage: React.FC = () => {
270
274
 
271
275
  {editingRole && (
272
276
  <Form.Item name="isActive" label={t('common.status')} valuePropName="checked">
273
- <Switch checkedChildren={t('common.enabled')} unCheckedChildren={t('common.disabled')} />
277
+ <Switch
278
+ checkedChildren={t('common.enabled')}
279
+ unCheckedChildren={t('common.disabled')}
280
+ />
274
281
  </Form.Item>
275
282
  )}
276
283
  </Form>
@@ -192,7 +192,9 @@ export const SystemLogsPage: React.FC = () => {
192
192
  {selectedLog && (
193
193
  <Descriptions column={1} bordered>
194
194
  <Descriptions.Item label={t('systemLogs.logId')}>{selectedLog.id}</Descriptions.Item>
195
- <Descriptions.Item label={t('systemLogs.userId')}>{selectedLog.userId}</Descriptions.Item>
195
+ <Descriptions.Item label={t('systemLogs.userId')}>
196
+ {selectedLog.userId}
197
+ </Descriptions.Item>
196
198
  <Descriptions.Item label={t('systemLogs.action')}>
197
199
  <Tag color={ACTION_COLORS[selectedLog.action] || 'default'}>
198
200
  {ACTION_LABELS[selectedLog.action] || selectedLog.action}
@@ -201,9 +203,15 @@ export const SystemLogsPage: React.FC = () => {
201
203
  <Descriptions.Item label={t('systemLogs.resourceType')}>
202
204
  {RESOURCE_LABELS[selectedLog.resourceType] || selectedLog.resourceType}
203
205
  </Descriptions.Item>
204
- <Descriptions.Item label={t('systemLogs.resourceId')}>{selectedLog.resourceId || '-'}</Descriptions.Item>
205
- <Descriptions.Item label={t('systemLogs.ipAddress')}>{selectedLog.ipAddress || '-'}</Descriptions.Item>
206
- <Descriptions.Item label={t('systemLogs.userAgent')}>{selectedLog.userAgent || '-'}</Descriptions.Item>
206
+ <Descriptions.Item label={t('systemLogs.resourceId')}>
207
+ {selectedLog.resourceId || '-'}
208
+ </Descriptions.Item>
209
+ <Descriptions.Item label={t('systemLogs.ipAddress')}>
210
+ {selectedLog.ipAddress || '-'}
211
+ </Descriptions.Item>
212
+ <Descriptions.Item label={t('systemLogs.userAgent')}>
213
+ {selectedLog.userAgent || '-'}
214
+ </Descriptions.Item>
207
215
  <Descriptions.Item label={t('systemLogs.time')}>
208
216
  {formatDate(selectedLog.createdAt)}
209
217
  </Descriptions.Item>
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react'
1
+ import { useState, useEffect, useCallback } from 'react'
2
2
  import { Table, Card, Tag, Button, Space, Modal, message, Descriptions } from 'antd'
3
3
  import { Eye, MessageCircle, CheckCircle, AlertCircle, Clock } from 'lucide-react'
4
4
  import { PermissionGuard } from '../components/PermissionGuard'
@@ -50,11 +50,7 @@ export const TicketsPage: React.FC = () => {
50
50
  return map[status] || status
51
51
  }
52
52
 
53
- useEffect(() => {
54
- fetchTickets()
55
- }, [])
56
-
57
- const fetchTickets = async () => {
53
+ const fetchTickets = useCallback(async () => {
58
54
  setLoading(true)
59
55
  try {
60
56
  const response = await apiClient.api.tickets.$get({ query: {} })
@@ -69,7 +65,11 @@ export const TicketsPage: React.FC = () => {
69
65
  } finally {
70
66
  setLoading(false)
71
67
  }
72
- }
68
+ }, [t])
69
+
70
+ useEffect(() => {
71
+ fetchTickets()
72
+ }, [fetchTickets])
73
73
 
74
74
  const handleClose = async (ticketId: string) => {
75
75
  Modal.confirm({
@@ -131,9 +131,7 @@ export const TicketsPage: React.FC = () => {
131
131
  title: t('common.status'),
132
132
  dataIndex: 'status',
133
133
  key: 'status',
134
- render: (status: string) => (
135
- <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>
136
- ),
134
+ render: (status: string) => <Tag color={STATUS_COLORS[status]}>{getStatusLabel(status)}</Tag>,
137
135
  },
138
136
  {
139
137
  title: t('common.createdAt'),
@@ -257,8 +255,12 @@ export const TicketsPage: React.FC = () => {
257
255
  {getStatusLabel(selectedTicket.status)}
258
256
  </Tag>
259
257
  </Descriptions.Item>
260
- <Descriptions.Item label={t('tickets.customerName')}>{selectedTicket.customerName}</Descriptions.Item>
261
- <Descriptions.Item label={t('tickets.customerEmail')}>{selectedTicket.customerEmail}</Descriptions.Item>
258
+ <Descriptions.Item label={t('tickets.customerName')}>
259
+ {selectedTicket.customerName}
260
+ </Descriptions.Item>
261
+ <Descriptions.Item label={t('tickets.customerEmail')}>
262
+ {selectedTicket.customerEmail}
263
+ </Descriptions.Item>
262
264
  <Descriptions.Item label={t('tickets.priority')}>
263
265
  <Tag color={PRIORITY_COLORS[selectedTicket.priority]}>
264
266
  {getPriorityLabel(selectedTicket.priority)}
@@ -285,9 +287,7 @@ export const TicketsPage: React.FC = () => {
285
287
  >
286
288
  <div className="flex justify-between items-center mb-1">
287
289
  <span className="font-medium">{reply.author}</span>
288
- <span className="text-sm text-gray-500">
289
- {formatDate(reply.createdAt)}
290
- </span>
290
+ <span className="text-sm text-gray-500">{formatDate(reply.createdAt)}</span>
291
291
  </div>
292
292
  <div className="text-gray-700">{reply.content}</div>
293
293
  </div>