@famgia/omnify-typescript 0.0.67 → 0.0.69
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/dist/{chunk-4L77AHAC.js → chunk-6I4O23X6.js} +521 -66
- package/dist/chunk-6I4O23X6.js.map +1 -0
- package/dist/index.cjs +761 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -2
- package/dist/index.d.ts +138 -2
- package/dist/index.js +227 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +624 -75
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +96 -11
- package/dist/plugin.js.map +1 -1
- package/package.json +3 -3
- package/scripts/postinstall.js +29 -40
- package/stubs/JapaneseAddressField.tsx.stub +289 -0
- package/stubs/JapaneseBankField.tsx.stub +212 -0
- package/stubs/JapaneseNameField.tsx.stub +194 -0
- package/stubs/ai-guides/checklists/react.md.stub +108 -0
- package/stubs/ai-guides/cursor/react-design.mdc.stub +289 -0
- package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
- package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
- package/stubs/ai-guides/cursor/react.mdc.stub +254 -0
- package/stubs/ai-guides/react/README.md.stub +221 -0
- package/stubs/ai-guides/react/antd-guide.md.stub +294 -0
- package/stubs/ai-guides/react/checklist.md.stub +108 -0
- package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
- package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
- package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
- package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
- package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
- package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
- package/stubs/ai-guides/react/types-guide.md.stub +524 -0
- package/stubs/components-index.ts.stub +13 -0
- package/stubs/form-validation.ts.stub +106 -0
- package/stubs/rules/index.ts.stub +48 -0
- package/stubs/rules/kana.ts.stub +291 -0
- package/stubs/use-form-mutation.ts.stub +117 -0
- package/stubs/zod-i18n.ts.stub +32 -0
- package/ai-guides/antdesign-guide.md +0 -401
- package/ai-guides/typescript-guide.md +0 -310
- package/dist/chunk-4L77AHAC.js.map +0 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
# TypeScript Types Guide
|
|
2
|
+
|
|
3
|
+
> This guide defines where and how to define types in this project.
|
|
4
|
+
|
|
5
|
+
## Type Categories
|
|
6
|
+
|
|
7
|
+
| Category | Location | Generated | Example |
|
|
8
|
+
| ------------------- | -------------------- | --------- | ----------------------------- |
|
|
9
|
+
| **Model** | `@/types/model` | ✅ Omnify | `User`, `Post` |
|
|
10
|
+
| **Create/Update** | `@/types/model` | ✅ Omnify | `UserCreate`, `UserUpdate` |
|
|
11
|
+
| **Common** | `@/types/model` | ✅ Omnify | `DateTimeString`, `LocaleMap` |
|
|
12
|
+
| **Validation** | `@/types/model` | ✅ Omnify | `getUserRules(locale)` |
|
|
13
|
+
| **Enum** | `@/types/model/enum` | ✅ Omnify | `PostStatus`, `UserRole` |
|
|
14
|
+
| **API Params** | Service file | ❌ Manual | `UserListParams` |
|
|
15
|
+
| **API Response** | `@/lib/api.ts` | ❌ Manual | `PaginatedResponse<T>` |
|
|
16
|
+
| **Component Props** | Component file | ❌ Manual | `UserTableProps` |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Model Types (Omnify)
|
|
21
|
+
|
|
22
|
+
**Location**: `src/types/model/`
|
|
23
|
+
|
|
24
|
+
**Source**: Auto-generated from `.omnify/schemas/`
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// ✅ Import from @/types/model
|
|
28
|
+
import type { User, UserCreate, UserUpdate } from "@/types/model";
|
|
29
|
+
import type { DateTimeString } from "@/types/model";
|
|
30
|
+
import { getUserRules } from "@/types/model";
|
|
31
|
+
|
|
32
|
+
// ❌ DON'T define model types manually
|
|
33
|
+
interface User { ... } // WRONG - already generated
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/types/model/
|
|
40
|
+
├── common.ts ❌ DO NOT EDIT
|
|
41
|
+
│ # LocaleMap, ValidationRule, DateTimeString
|
|
42
|
+
├── base/ ❌ DO NOT EDIT
|
|
43
|
+
│ └── User.ts # User + UserCreate + UserUpdate
|
|
44
|
+
├── rules/ ❌ DO NOT EDIT
|
|
45
|
+
│ └── User.rules.ts # getUserRules(), getUserDisplayName()
|
|
46
|
+
├── enum/ ❌ DO NOT EDIT (if exists)
|
|
47
|
+
│ └── PostStatus.ts
|
|
48
|
+
├── index.ts ❌ DO NOT EDIT (re-exports)
|
|
49
|
+
└── User.ts ✅ CAN EDIT (extension)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Generated Types Per Model
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Auto-generated in base/User.ts:
|
|
56
|
+
interface User {
|
|
57
|
+
id: number;
|
|
58
|
+
name: string;
|
|
59
|
+
email: string;
|
|
60
|
+
created_at?: DateTimeString; // Uses DateTimeString
|
|
61
|
+
updated_at?: DateTimeString;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type UserCreate = Omit<User, 'id' | 'created_at' | 'updated_at'>;
|
|
65
|
+
type UserUpdate = Partial<UserCreate>;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Extending Model Types
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// src/types/model/User.ts (safe to edit)
|
|
72
|
+
import type { User as UserBase } from "./base/User.js";
|
|
73
|
+
|
|
74
|
+
export interface User extends UserBase {
|
|
75
|
+
// Frontend-only computed properties
|
|
76
|
+
fullName?: string;
|
|
77
|
+
|
|
78
|
+
// UI state
|
|
79
|
+
isSelected?: boolean;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 2. Using Generated Types
|
|
86
|
+
|
|
87
|
+
### Create/Update Types
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ✅ Use Omnify-generated types
|
|
91
|
+
import type { User, UserCreate, UserUpdate } from "@/types/model";
|
|
92
|
+
|
|
93
|
+
const userService = {
|
|
94
|
+
create: (input: UserCreate) => api.post("/api/users", input),
|
|
95
|
+
update: (id: number, input: UserUpdate) => api.put(`/api/users/${id}`, input),
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Validation Rules with Ant Design Form.Item
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { userSchemas, getCustomerFieldLabel } from "@/types/model/User";
|
|
103
|
+
import { zodRule } from "@/lib/form-validation";
|
|
104
|
+
import { useLocale } from "next-intl";
|
|
105
|
+
|
|
106
|
+
function UserForm() {
|
|
107
|
+
const locale = useLocale();
|
|
108
|
+
const label = (key: string) => getCustomerFieldLabel(key, locale);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Form>
|
|
112
|
+
{/* Name */}
|
|
113
|
+
<Form.Item
|
|
114
|
+
name="name"
|
|
115
|
+
label={label("name")}
|
|
116
|
+
rules={[zodRule(userSchemas.name, label("name"))]}
|
|
117
|
+
>
|
|
118
|
+
<Input />
|
|
119
|
+
</Form.Item>
|
|
120
|
+
|
|
121
|
+
{/* Email */}
|
|
122
|
+
<Form.Item
|
|
123
|
+
name="email"
|
|
124
|
+
label={label("email")}
|
|
125
|
+
rules={[zodRule(userSchemas.email, label("email"))]}
|
|
126
|
+
>
|
|
127
|
+
<Input />
|
|
128
|
+
</Form.Item>
|
|
129
|
+
</Form>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Key Points:**
|
|
135
|
+
- Import `{model}Schemas` for Zod validation schemas
|
|
136
|
+
- Import `zodRule` from `@/lib/form-validation`
|
|
137
|
+
- Use `zodRule(schema, displayName)` in Form.Item rules
|
|
138
|
+
- Comment `{/* Field Name */}` before each Form.Item for clarity
|
|
139
|
+
|
|
140
|
+
### DateTimeString
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import type { DateTimeString } from "@/types/model";
|
|
144
|
+
import { formatDateTime } from "@/lib/dayjs";
|
|
145
|
+
|
|
146
|
+
interface Event {
|
|
147
|
+
scheduled_at: DateTimeString; // ISO 8601 UTC string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Display
|
|
151
|
+
formatDateTime(event.scheduled_at); // "2024/01/15 19:30"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 3. API Params Types (Manual)
|
|
157
|
+
|
|
158
|
+
**Location**: Service file (colocated)
|
|
159
|
+
|
|
160
|
+
**Only define query params (not in Omnify):**
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// services/users.ts
|
|
164
|
+
import type { User, UserCreate, UserUpdate } from "@/types/model";
|
|
165
|
+
|
|
166
|
+
/** Query params for listing users (GET /api/users) */
|
|
167
|
+
export interface UserListParams {
|
|
168
|
+
search?: string;
|
|
169
|
+
role?: string;
|
|
170
|
+
page?: number;
|
|
171
|
+
per_page?: number;
|
|
172
|
+
sort_by?: keyof User;
|
|
173
|
+
sort_order?: "asc" | "desc";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const userService = {
|
|
177
|
+
list: (params?: UserListParams) => ...,
|
|
178
|
+
create: (input: UserCreate) => ..., // ← Use Omnify type
|
|
179
|
+
update: (id: number, input: UserUpdate) => ..., // ← Use Omnify type
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 4. API Response Types
|
|
186
|
+
|
|
187
|
+
**Location**: `src/lib/api.ts`
|
|
188
|
+
|
|
189
|
+
**Naming**: `{Name}Response`, `Paginated{Name}`
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// lib/api.ts
|
|
193
|
+
|
|
194
|
+
/** Laravel paginated response */
|
|
195
|
+
export interface PaginatedResponse<T> {
|
|
196
|
+
data: T[];
|
|
197
|
+
links: {
|
|
198
|
+
first: string | null;
|
|
199
|
+
last: string | null;
|
|
200
|
+
prev: string | null;
|
|
201
|
+
next: string | null;
|
|
202
|
+
};
|
|
203
|
+
meta: {
|
|
204
|
+
current_page: number;
|
|
205
|
+
from: number | null;
|
|
206
|
+
last_page: number;
|
|
207
|
+
per_page: number;
|
|
208
|
+
to: number | null;
|
|
209
|
+
total: number;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Laravel single resource response */
|
|
214
|
+
export interface ResourceResponse<T> {
|
|
215
|
+
data: T;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Laravel validation error (422) */
|
|
219
|
+
export interface ValidationError {
|
|
220
|
+
message: string;
|
|
221
|
+
errors: Record<string, string[]>;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Usage in Service
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import api, { PaginatedResponse } from "@/lib/api";
|
|
229
|
+
import type { User } from "@/types/model";
|
|
230
|
+
|
|
231
|
+
export const userService = {
|
|
232
|
+
list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
|
|
233
|
+
const { data } = await api.get("/api/users", { params });
|
|
234
|
+
return data;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 4. Component Props Types
|
|
242
|
+
|
|
243
|
+
**Location**: Same file as component (inline)
|
|
244
|
+
|
|
245
|
+
**Naming**: `{Component}Props`
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// components/tables/UserTable.tsx
|
|
249
|
+
|
|
250
|
+
import type { User } from "@/types/model";
|
|
251
|
+
import type { PaginatedResponse } from "@/lib/api";
|
|
252
|
+
|
|
253
|
+
// ─────────────────────────────────────────────────────────────────
|
|
254
|
+
// Props - Define at top of file
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
interface UserTableProps {
|
|
258
|
+
users: User[];
|
|
259
|
+
loading?: boolean;
|
|
260
|
+
pagination?: PaginatedResponse<User>["meta"];
|
|
261
|
+
onPageChange?: (page: number) => void;
|
|
262
|
+
onEdit?: (user: User) => void;
|
|
263
|
+
onDelete?: (user: User) => void;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────
|
|
267
|
+
// Component
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
export function UserTable({
|
|
271
|
+
users,
|
|
272
|
+
loading = false,
|
|
273
|
+
pagination,
|
|
274
|
+
onPageChange,
|
|
275
|
+
onEdit,
|
|
276
|
+
onDelete,
|
|
277
|
+
}: UserTableProps) {
|
|
278
|
+
return <Table ... />;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### When to Export Props
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// ✅ Export if other components need it
|
|
286
|
+
export interface UserTableProps { ... }
|
|
287
|
+
|
|
288
|
+
// ✅ Don't export if only used internally
|
|
289
|
+
interface UserTableProps { ... }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 5. Hook Types
|
|
295
|
+
|
|
296
|
+
**Location**: Hook file (inline or inferred)
|
|
297
|
+
|
|
298
|
+
**Approach**: Let TypeScript infer return types when possible
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// hooks/useUsers.ts
|
|
302
|
+
|
|
303
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
304
|
+
import { userService, UserCreateInput } from "@/services/users";
|
|
305
|
+
import { queryKeys } from "@/lib/queryKeys";
|
|
306
|
+
|
|
307
|
+
export function useUsers(params?: UserListParams) {
|
|
308
|
+
// Return type is inferred from userService.list
|
|
309
|
+
return useQuery({
|
|
310
|
+
queryKey: queryKeys.users.list(params),
|
|
311
|
+
queryFn: () => userService.list(params),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function useCreateUser() {
|
|
316
|
+
const queryClient = useQueryClient();
|
|
317
|
+
|
|
318
|
+
// Return type is inferred from useMutation
|
|
319
|
+
return useMutation({
|
|
320
|
+
mutationFn: (input: UserCreateInput) => userService.create(input),
|
|
321
|
+
onSuccess: () => {
|
|
322
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### When to Define Return Type
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// ✅ Let TypeScript infer (simpler, less maintenance)
|
|
332
|
+
export function useUsers(params?: UserListParams) {
|
|
333
|
+
return useQuery({ ... });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ✅ Define explicitly if complex or for documentation
|
|
337
|
+
export function useAuth(): {
|
|
338
|
+
user: User | undefined;
|
|
339
|
+
isLoading: boolean;
|
|
340
|
+
login: (input: LoginInput) => Promise<void>;
|
|
341
|
+
logout: () => Promise<void>;
|
|
342
|
+
} {
|
|
343
|
+
...
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 6. Shared/Utility Types
|
|
350
|
+
|
|
351
|
+
**Location**: `src/types/index.ts` (only if used across many files)
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// types/index.ts
|
|
355
|
+
|
|
356
|
+
/** Common ID type */
|
|
357
|
+
export type ID = number;
|
|
358
|
+
|
|
359
|
+
/** Nullable type helper */
|
|
360
|
+
export type Nullable<T> = T | null;
|
|
361
|
+
|
|
362
|
+
/** Make specific keys optional */
|
|
363
|
+
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
364
|
+
|
|
365
|
+
/** Extract array element type */
|
|
366
|
+
export type ArrayElement<T> = T extends (infer U)[] ? U : never;
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### When to Use Shared Types
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// ✅ Use shared types for truly common patterns
|
|
373
|
+
import type { ID, Nullable } from "@/types";
|
|
374
|
+
|
|
375
|
+
interface Post {
|
|
376
|
+
id: ID;
|
|
377
|
+
author_id: ID;
|
|
378
|
+
published_at: Nullable<string>;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ❌ Don't over-abstract
|
|
382
|
+
// Bad: Creating shared types for every little thing
|
|
383
|
+
export type UserName = string; // Just use string
|
|
384
|
+
export type UserId = number; // Just use number
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Type Definition Checklist
|
|
390
|
+
|
|
391
|
+
### Before Creating a Type
|
|
392
|
+
|
|
393
|
+
1. **Is it a Model?** → Use `@/types/model` (Omnify)
|
|
394
|
+
2. **Is it API input?** → Define in service file
|
|
395
|
+
3. **Is it API response?** → Use/extend types in `lib/api.ts`
|
|
396
|
+
4. **Is it component props?** → Define in component file
|
|
397
|
+
5. **Is it used in 3+ places?** → Consider `types/index.ts`
|
|
398
|
+
|
|
399
|
+
### Type Naming Conventions
|
|
400
|
+
|
|
401
|
+
| Type | Pattern | Example |
|
|
402
|
+
| ------------ | -------------------- | ------------------- |
|
|
403
|
+
| Model | PascalCase | `User`, `Post` |
|
|
404
|
+
| Create Input | `{Model}CreateInput` | `UserCreateInput` |
|
|
405
|
+
| Update Input | `{Model}UpdateInput` | `UserUpdateInput` |
|
|
406
|
+
| List Params | `{Model}ListParams` | `UserListParams` |
|
|
407
|
+
| Props | `{Component}Props` | `UserTableProps` |
|
|
408
|
+
| Response | `{Name}Response` | `PaginatedResponse` |
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Complete Example
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
416
|
+
// types/model/User.ts (Omnify extension)
|
|
417
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
418
|
+
import type { User as UserBase } from "./base/User.js";
|
|
419
|
+
|
|
420
|
+
export interface User extends UserBase {
|
|
421
|
+
// Add frontend-only properties if needed
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
425
|
+
// services/users.ts
|
|
426
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
427
|
+
import api, { PaginatedResponse } from "@/lib/api";
|
|
428
|
+
import type { User } from "@/types/model";
|
|
429
|
+
|
|
430
|
+
export interface UserCreateInput {
|
|
431
|
+
name: string;
|
|
432
|
+
email: string;
|
|
433
|
+
password: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export interface UserUpdateInput {
|
|
437
|
+
name?: string;
|
|
438
|
+
email?: string;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface UserListParams {
|
|
442
|
+
search?: string;
|
|
443
|
+
page?: number;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export const userService = {
|
|
447
|
+
list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
|
|
448
|
+
const { data } = await api.get("/api/users", { params });
|
|
449
|
+
return data;
|
|
450
|
+
},
|
|
451
|
+
get: async (id: number): Promise<User> => {
|
|
452
|
+
const { data } = await api.get(`/api/users/${id}`);
|
|
453
|
+
return data.data ?? data;
|
|
454
|
+
},
|
|
455
|
+
create: async (input: UserCreateInput): Promise<User> => {
|
|
456
|
+
const { data } = await api.post("/api/users", input);
|
|
457
|
+
return data.data ?? data;
|
|
458
|
+
},
|
|
459
|
+
update: async (id: number, input: UserUpdateInput): Promise<User> => {
|
|
460
|
+
const { data } = await api.put(`/api/users/${id}`, input);
|
|
461
|
+
return data.data ?? data;
|
|
462
|
+
},
|
|
463
|
+
delete: async (id: number): Promise<void> => {
|
|
464
|
+
await api.delete(`/api/users/${id}`);
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
469
|
+
// components/tables/UserTable.tsx
|
|
470
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
471
|
+
import type { User } from "@/types/model";
|
|
472
|
+
|
|
473
|
+
interface UserTableProps {
|
|
474
|
+
users: User[];
|
|
475
|
+
loading?: boolean;
|
|
476
|
+
onEdit?: (user: User) => void;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function UserTable({ users, loading, onEdit }: UserTableProps) {
|
|
480
|
+
return <Table dataSource={users} loading={loading} ... />;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
484
|
+
// app/(dashboard)/users/page.tsx
|
|
485
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
486
|
+
"use client";
|
|
487
|
+
|
|
488
|
+
import { useQuery } from "@tanstack/react-query";
|
|
489
|
+
import { userService, UserListParams } from "@/services/users";
|
|
490
|
+
import { UserTable } from "@/components/tables/UserTable";
|
|
491
|
+
import { queryKeys } from "@/lib/queryKeys";
|
|
492
|
+
|
|
493
|
+
export default function UsersPage() {
|
|
494
|
+
const [params, setParams] = useState<UserListParams>({ page: 1 });
|
|
495
|
+
|
|
496
|
+
const { data, isLoading } = useQuery({
|
|
497
|
+
queryKey: queryKeys.users.list(params),
|
|
498
|
+
queryFn: () => userService.list(params),
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
return (
|
|
502
|
+
<UserTable
|
|
503
|
+
users={data?.data ?? []}
|
|
504
|
+
loading={isLoading}
|
|
505
|
+
onEdit={(user) => router.push(`/users/${user.id}/edit`)}
|
|
506
|
+
/>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Summary
|
|
514
|
+
|
|
515
|
+
| Type | Location | Why |
|
|
516
|
+
| -------- | -------------------- | ------------------------- |
|
|
517
|
+
| Model | `@/types/model` | Synced with DB via Omnify |
|
|
518
|
+
| Input | Service file | Colocated with API logic |
|
|
519
|
+
| Response | `lib/api.ts` | Shared Laravel patterns |
|
|
520
|
+
| Props | Component file | Colocated with component |
|
|
521
|
+
| Hook | Hook file (inferred) | TypeScript handles it |
|
|
522
|
+
| Utility | `types/index.ts` | Only if widely used |
|
|
523
|
+
|
|
524
|
+
**Philosophy**: Keep types close to their usage. Don't over-organize.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JapaneseNameField } from './JapaneseNameField.js';
|
|
2
|
+
import { JapaneseAddressField } from './JapaneseAddressField.js';
|
|
3
|
+
import { JapaneseBankField } from './JapaneseBankField.js';
|
|
4
|
+
|
|
5
|
+
// Namespace style exports (recommended)
|
|
6
|
+
export const OmnifyForm = {
|
|
7
|
+
JapaneseName: JapaneseNameField,
|
|
8
|
+
JapaneseAddress: JapaneseAddressField,
|
|
9
|
+
JapaneseBank: JapaneseBankField,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
// Legacy exports (backward compatible)
|
|
13
|
+
export { JapaneseNameField, JapaneseAddressField, JapaneseBankField };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form validation utilities for Ant Design + Zod
|
|
3
|
+
* Generated by Omnify. You can customize this file.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RuleObject } from 'antd/es/form';
|
|
7
|
+
import type { z } from 'zod';
|
|
8
|
+
import { getZodMessage } from './zod-i18n.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert Zod schema to Ant Design Form rule with i18n support
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Set locale once at component level
|
|
15
|
+
* setZodLocale('ja');
|
|
16
|
+
*
|
|
17
|
+
* // Use without passing locale
|
|
18
|
+
* <Form.Item
|
|
19
|
+
* name="email"
|
|
20
|
+
* rules={[zodRule(customerSchemas.email, 'メールアドレス')]}
|
|
21
|
+
* >
|
|
22
|
+
* <Input />
|
|
23
|
+
* </Form.Item>
|
|
24
|
+
*/
|
|
25
|
+
export function zodRule<T extends z.ZodTypeAny>(
|
|
26
|
+
schema: T,
|
|
27
|
+
displayName?: string
|
|
28
|
+
): RuleObject {
|
|
29
|
+
const field = displayName ?? 'この項目';
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
validator: async (_, value) => {
|
|
33
|
+
// Empty check - treat as required
|
|
34
|
+
if (value === undefined || value === null || value === '') {
|
|
35
|
+
if (schema.safeParse(undefined).success) return;
|
|
36
|
+
throw new Error(getZodMessage('required', { displayName: field }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = schema.safeParse(value);
|
|
40
|
+
if (result.success) return;
|
|
41
|
+
|
|
42
|
+
// Get first Zod error
|
|
43
|
+
const issue = result.error.issues[0];
|
|
44
|
+
if (!issue) {
|
|
45
|
+
throw new Error(getZodMessage('required', { displayName: field }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Translate based on error type
|
|
49
|
+
switch (issue.code) {
|
|
50
|
+
case 'too_small':
|
|
51
|
+
if (issue.type === 'string' && issue.minimum === 1) {
|
|
52
|
+
throw new Error(getZodMessage('required', { displayName: field }));
|
|
53
|
+
}
|
|
54
|
+
if (issue.type === 'string') {
|
|
55
|
+
throw new Error(getZodMessage('minLength', { displayName: field, min: issue.minimum as number }));
|
|
56
|
+
}
|
|
57
|
+
throw new Error(getZodMessage('min', { displayName: field, min: issue.minimum as number }));
|
|
58
|
+
|
|
59
|
+
case 'too_big':
|
|
60
|
+
if (issue.type === 'string') {
|
|
61
|
+
throw new Error(getZodMessage('maxLength', { displayName: field, max: issue.maximum as number }));
|
|
62
|
+
}
|
|
63
|
+
throw new Error(getZodMessage('max', { displayName: field, max: issue.maximum as number }));
|
|
64
|
+
|
|
65
|
+
case 'invalid_string':
|
|
66
|
+
if (issue.validation === 'email') {
|
|
67
|
+
throw new Error(getZodMessage('email', { displayName: field }));
|
|
68
|
+
}
|
|
69
|
+
if (issue.validation === 'url') {
|
|
70
|
+
throw new Error(getZodMessage('url', { displayName: field }));
|
|
71
|
+
}
|
|
72
|
+
if (issue.validation === 'regex') {
|
|
73
|
+
throw new Error(getZodMessage('pattern', { displayName: field }));
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case 'invalid_type':
|
|
78
|
+
if (issue.received === 'undefined' || issue.received === 'null') {
|
|
79
|
+
throw new Error(getZodMessage('required', { displayName: field }));
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fallback to Zod's original message
|
|
85
|
+
throw new Error(issue.message);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create required rule with i18n message
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* <Form.Item
|
|
95
|
+
* name="name"
|
|
96
|
+
* rules={[requiredRule('名前')]}
|
|
97
|
+
* >
|
|
98
|
+
* <Input />
|
|
99
|
+
* </Form.Item>
|
|
100
|
+
*/
|
|
101
|
+
export function requiredRule(displayName: string): RuleObject {
|
|
102
|
+
return {
|
|
103
|
+
required: true,
|
|
104
|
+
message: getZodMessage('required', { displayName }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Japanese validation rules
|
|
3
|
+
*
|
|
4
|
+
* Usage with Zod:
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { z } from 'zod';
|
|
7
|
+
* import { kanaString, KATAKANA_PATTERN } from '@/omnify/lib/rules';
|
|
8
|
+
*
|
|
9
|
+
* // Method 1: Use kanaString helper
|
|
10
|
+
* const schema = z.object({
|
|
11
|
+
* name_kana: kanaString(), // 全角カタカナ (default)
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Method 2: Use pattern directly
|
|
15
|
+
* const schema2 = z.object({
|
|
16
|
+
* name_kana: z.string().regex(KATAKANA_PATTERN, '全角カタカナで入力してください'),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
// Kana validation
|
|
23
|
+
kanaRules,
|
|
24
|
+
createKanaRegex,
|
|
25
|
+
validateKana,
|
|
26
|
+
getKanaPattern,
|
|
27
|
+
getKanaErrorMessage,
|
|
28
|
+
// Zod helpers
|
|
29
|
+
kanaString,
|
|
30
|
+
withKana,
|
|
31
|
+
// Pattern constants
|
|
32
|
+
KATAKANA_PATTERN,
|
|
33
|
+
KATAKANA_HALF_PATTERN,
|
|
34
|
+
HIRAGANA_PATTERN,
|
|
35
|
+
KANA_ANY_PATTERN,
|
|
36
|
+
// Presets
|
|
37
|
+
KATAKANA_FULL_WIDTH,
|
|
38
|
+
KATAKANA_HALF_WIDTH,
|
|
39
|
+
HIRAGANA,
|
|
40
|
+
KANA_ANY,
|
|
41
|
+
KATAKANA_WITH_NUMBERS,
|
|
42
|
+
// Types
|
|
43
|
+
type KanaRuleOptions,
|
|
44
|
+
} from './kana';
|
|
45
|
+
|
|
46
|
+
// Re-export as convenient aliases
|
|
47
|
+
export { getKanaPattern as kanaPattern } from './kana';
|
|
48
|
+
export { createKanaRegex as kanaRegex } from './kana';
|