@famgia/omnify-typescript 0.0.145 → 0.0.147
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-2Q2T4NRP.js → chunk-PA7B7ZFK.js} +30 -18
- package/dist/chunk-PA7B7ZFK.js.map +1 -0
- package/dist/index.cjs +30 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -40
- package/dist/index.d.ts +56 -40
- package/dist/index.js +2 -62
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +61 -23
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +33 -7
- package/dist/plugin.js.map +1 -1
- package/package.json +11 -11
- package/dist/chunk-2Q2T4NRP.js.map +0 -1
- package/stubs/JapaneseAddressField.tsx.stub +0 -289
- package/stubs/JapaneseBankField.tsx.stub +0 -212
- package/stubs/JapaneseNameField.tsx.stub +0 -194
- package/stubs/components-index.ts.stub +0 -13
- package/stubs/form-validation.ts.stub +0 -123
- package/stubs/rules/index.ts.stub +0 -51
- package/stubs/rules/kana.ts.stub +0 -294
- package/stubs/use-form-mutation.ts.stub +0 -119
- package/stubs/zod-i18n.ts.stub +0 -34
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JapaneseAddressField - Japanese address input component
|
|
3
|
-
* Handles postal code, prefecture, address fields with postal code lookup
|
|
4
|
-
*/
|
|
5
|
-
import { useState, useCallback } from 'react';
|
|
6
|
-
import { Form, Input, Select, Button, message } from 'antd';
|
|
7
|
-
import { SearchOutlined, LoadingOutlined } from '@ant-design/icons';
|
|
8
|
-
import type { FormInstance } from 'antd';
|
|
9
|
-
import type { RuleObject } from 'antd/es/form';
|
|
10
|
-
import { zodRule } from '../lib/form-validation';
|
|
11
|
-
import { getZodLocale } from '../lib/zod-i18n';
|
|
12
|
-
import { Prefecture, PrefectureValues, getPrefectureLabel, getPrefectureExtra } from '../enum/plugin/Prefecture';
|
|
13
|
-
|
|
14
|
-
interface I18nConfig {
|
|
15
|
-
fields: Record<string, { label?: Record<string, string>; placeholder?: Record<string, string> }>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface SelectOption {
|
|
19
|
-
value: string | number;
|
|
20
|
-
label: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface PostalLookupResult {
|
|
24
|
-
prefecture?: string;
|
|
25
|
-
prefectureCode?: string;
|
|
26
|
-
prefectureName?: string;
|
|
27
|
-
address1?: string;
|
|
28
|
-
address2?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface JapaneseAddressFieldProps {
|
|
32
|
-
form: FormInstance;
|
|
33
|
-
schemas: Record<string, unknown>;
|
|
34
|
-
i18n: I18nConfig;
|
|
35
|
-
prefix?: string;
|
|
36
|
-
usePrefectureId?: boolean;
|
|
37
|
-
prefectureOptions?: SelectOption[];
|
|
38
|
-
enablePostalLookup?: boolean;
|
|
39
|
-
showSearchButton?: boolean;
|
|
40
|
-
autoSearch?: boolean;
|
|
41
|
-
onPostalLookup?: (postalCode: string) => Promise<PostalLookupResult | null>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getLabel(i18n: I18nConfig, field: string, locale: string): string {
|
|
45
|
-
return i18n.fields[field]?.label?.[locale] ?? i18n.fields[field]?.label?.['en'] ?? field;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getPlaceholder(i18n: I18nConfig, field: string, locale: string): string {
|
|
49
|
-
return i18n.fields[field]?.placeholder?.[locale] ?? i18n.fields[field]?.placeholder?.['en'] ?? '';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Build prefecture code to string key mapping from generated enum
|
|
53
|
-
const PREFECTURE_CODE_TO_KEY: Record<string, string> = Object.fromEntries(
|
|
54
|
-
PrefectureValues.map((key) => {
|
|
55
|
-
const extra = getPrefectureExtra(key);
|
|
56
|
-
return [String(extra?.code ?? ''), key];
|
|
57
|
-
})
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// Build default prefecture options from generated enum
|
|
61
|
-
function buildPrefectureOptions(locale: string): SelectOption[] {
|
|
62
|
-
return PrefectureValues.map((key) => ({
|
|
63
|
-
value: key,
|
|
64
|
-
label: getPrefectureLabel(key, locale),
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Default postal lookup function using zipcloud API
|
|
69
|
-
async function lookupPostalCode(postalCode: string): Promise<PostalLookupResult | null> {
|
|
70
|
-
try {
|
|
71
|
-
const response = await fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${postalCode}`);
|
|
72
|
-
const data = await response.json();
|
|
73
|
-
|
|
74
|
-
if (data.results && data.results.length > 0) {
|
|
75
|
-
const result = data.results[0];
|
|
76
|
-
const prefectureKey = PREFECTURE_CODE_TO_KEY[result.prefcode];
|
|
77
|
-
return {
|
|
78
|
-
prefecture: prefectureKey,
|
|
79
|
-
prefectureCode: result.prefcode,
|
|
80
|
-
prefectureName: result.address1,
|
|
81
|
-
address1: result.address2,
|
|
82
|
-
address2: result.address3,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error('Postal code lookup failed:', error);
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Localized messages
|
|
93
|
-
const MESSAGES = {
|
|
94
|
-
ja: {
|
|
95
|
-
searchAddress: '住所検索',
|
|
96
|
-
searching: '検索中...',
|
|
97
|
-
notFound: '郵便番号が見つかりませんでした',
|
|
98
|
-
error: '住所検索に失敗しました',
|
|
99
|
-
invalidFormat: '郵便番号の形式が正しくありません(例:123-4567)',
|
|
100
|
-
},
|
|
101
|
-
en: {
|
|
102
|
-
searchAddress: 'Search Address',
|
|
103
|
-
searching: 'Searching...',
|
|
104
|
-
notFound: 'Postal code not found',
|
|
105
|
-
error: 'Address lookup failed',
|
|
106
|
-
invalidFormat: 'Invalid postal code format (e.g., 123-4567)',
|
|
107
|
-
},
|
|
108
|
-
vi: {
|
|
109
|
-
searchAddress: 'Tìm địa chỉ',
|
|
110
|
-
searching: 'Đang tìm...',
|
|
111
|
-
notFound: 'Không tìm thấy mã bưu điện',
|
|
112
|
-
error: 'Tìm địa chỉ thất bại',
|
|
113
|
-
invalidFormat: 'Định dạng mã bưu điện không hợp lệ (VD: 123-4567)',
|
|
114
|
-
},
|
|
115
|
-
} as const;
|
|
116
|
-
|
|
117
|
-
function getMessage(key: keyof typeof MESSAGES.ja, locale: string): string {
|
|
118
|
-
return (MESSAGES as any)[locale]?.[key] ?? MESSAGES.ja[key];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function JapaneseAddressField({
|
|
122
|
-
form,
|
|
123
|
-
schemas,
|
|
124
|
-
i18n,
|
|
125
|
-
prefix = 'address',
|
|
126
|
-
usePrefectureId = false,
|
|
127
|
-
prefectureOptions,
|
|
128
|
-
enablePostalLookup = true,
|
|
129
|
-
showSearchButton = true,
|
|
130
|
-
autoSearch = true,
|
|
131
|
-
onPostalLookup,
|
|
132
|
-
}: JapaneseAddressFieldProps) {
|
|
133
|
-
const locale = getZodLocale();
|
|
134
|
-
const [isSearching, setIsSearching] = useState(false);
|
|
135
|
-
|
|
136
|
-
// Field names
|
|
137
|
-
const postalCodeField = `${prefix}_postal_code`;
|
|
138
|
-
const prefectureField = usePrefectureId
|
|
139
|
-
? `${prefix}_prefecture_id`
|
|
140
|
-
: `${prefix}_prefecture`;
|
|
141
|
-
const address1Field = `${prefix}_address1`;
|
|
142
|
-
const address2Field = `${prefix}_address2`;
|
|
143
|
-
const address3Field = `${prefix}_address3`;
|
|
144
|
-
|
|
145
|
-
// Use generated prefecture options
|
|
146
|
-
const options = prefectureOptions ?? buildPrefectureOptions(locale);
|
|
147
|
-
|
|
148
|
-
// Get rules from schemas
|
|
149
|
-
const getRule = (field: string): RuleObject[] => {
|
|
150
|
-
const schema = schemas[field];
|
|
151
|
-
if (!schema) return [];
|
|
152
|
-
return [zodRule(schema as any, getLabel(i18n, field, locale))];
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Check if a field is required by examining its Zod schema description
|
|
156
|
-
const isRequired = (field: string): boolean => {
|
|
157
|
-
const schema = schemas[field];
|
|
158
|
-
if (!schema) return false;
|
|
159
|
-
// Check if schema has .optional() or .nullable() - these make it not required
|
|
160
|
-
const schemaDesc = (schema as any)?._def?.typeName;
|
|
161
|
-
const inner = (schema as any)?._def?.innerType;
|
|
162
|
-
// ZodOptional or ZodNullable means not required
|
|
163
|
-
if (schemaDesc === 'ZodOptional' || schemaDesc === 'ZodNullable') return false;
|
|
164
|
-
if (inner?._def?.typeName === 'ZodOptional' || inner?._def?.typeName === 'ZodNullable') return false;
|
|
165
|
-
return true;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Lookup address from postal code
|
|
169
|
-
const doLookup = useCallback(async (postalCode: string) => {
|
|
170
|
-
const digits = postalCode.replace(/[^0-9]/g, '');
|
|
171
|
-
if (digits.length !== 7) {
|
|
172
|
-
message.warning(getMessage('invalidFormat', locale));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setIsSearching(true);
|
|
177
|
-
try {
|
|
178
|
-
const lookupFn = onPostalLookup ?? lookupPostalCode;
|
|
179
|
-
const result = await lookupFn(postalCode);
|
|
180
|
-
|
|
181
|
-
if (result) {
|
|
182
|
-
const prefectureValue = usePrefectureId ? result.prefectureCode : result.prefecture;
|
|
183
|
-
form.setFieldsValue({
|
|
184
|
-
[prefectureField]: prefectureValue,
|
|
185
|
-
[address1Field]: result.address1,
|
|
186
|
-
[address2Field]: result.address2,
|
|
187
|
-
});
|
|
188
|
-
} else {
|
|
189
|
-
message.info(getMessage('notFound', locale));
|
|
190
|
-
}
|
|
191
|
-
} catch (error) {
|
|
192
|
-
message.error(getMessage('error', locale));
|
|
193
|
-
console.error('Postal code lookup failed:', error);
|
|
194
|
-
} finally {
|
|
195
|
-
setIsSearching(false);
|
|
196
|
-
}
|
|
197
|
-
}, [form, locale, onPostalLookup, prefectureField, address1Field, address2Field, usePrefectureId]);
|
|
198
|
-
|
|
199
|
-
const handlePostalCodeChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
200
|
-
const postalCode = e.target.value.replace(/[^0-9]/g, '');
|
|
201
|
-
if (autoSearch && enablePostalLookup && postalCode.length === 7) {
|
|
202
|
-
await doLookup(postalCode);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const handleSearchClick = async () => {
|
|
207
|
-
const postalCode = form.getFieldValue(postalCodeField);
|
|
208
|
-
if (postalCode) {
|
|
209
|
-
await doLookup(postalCode);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<>
|
|
215
|
-
{/* 郵便番号 */}
|
|
216
|
-
<Form.Item
|
|
217
|
-
name={postalCodeField}
|
|
218
|
-
label={getLabel(i18n, postalCodeField, locale)}
|
|
219
|
-
rules={getRule(postalCodeField)}
|
|
220
|
-
required={isRequired(postalCodeField)}
|
|
221
|
-
>
|
|
222
|
-
<Input
|
|
223
|
-
placeholder={getPlaceholder(i18n, postalCodeField, locale)}
|
|
224
|
-
style={{ width: enablePostalLookup && showSearchButton ? 'calc(100% - 110px)' : 150 }}
|
|
225
|
-
onChange={handlePostalCodeChange}
|
|
226
|
-
addonAfter={
|
|
227
|
-
enablePostalLookup && showSearchButton && (
|
|
228
|
-
<Button
|
|
229
|
-
type="text"
|
|
230
|
-
size="small"
|
|
231
|
-
icon={isSearching ? <LoadingOutlined /> : <SearchOutlined />}
|
|
232
|
-
onClick={handleSearchClick}
|
|
233
|
-
disabled={isSearching}
|
|
234
|
-
>
|
|
235
|
-
{getMessage('searchAddress', locale)}
|
|
236
|
-
</Button>
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
/>
|
|
240
|
-
</Form.Item>
|
|
241
|
-
|
|
242
|
-
{/* 都道府県 */}
|
|
243
|
-
<Form.Item
|
|
244
|
-
name={prefectureField}
|
|
245
|
-
label={getLabel(i18n, prefectureField, locale)}
|
|
246
|
-
rules={getRule(prefectureField)}
|
|
247
|
-
required={isRequired(prefectureField)}
|
|
248
|
-
>
|
|
249
|
-
<Select
|
|
250
|
-
placeholder={getPlaceholder(i18n, prefectureField, locale)}
|
|
251
|
-
options={options}
|
|
252
|
-
style={{ width: 200 }}
|
|
253
|
-
showSearch
|
|
254
|
-
optionFilterProp="label"
|
|
255
|
-
/>
|
|
256
|
-
</Form.Item>
|
|
257
|
-
|
|
258
|
-
{/* 市区町村 */}
|
|
259
|
-
<Form.Item
|
|
260
|
-
name={address1Field}
|
|
261
|
-
label={getLabel(i18n, address1Field, locale)}
|
|
262
|
-
rules={getRule(address1Field)}
|
|
263
|
-
required={isRequired(address1Field)}
|
|
264
|
-
>
|
|
265
|
-
<Input placeholder={getPlaceholder(i18n, address1Field, locale)} />
|
|
266
|
-
</Form.Item>
|
|
267
|
-
|
|
268
|
-
{/* 番地 */}
|
|
269
|
-
<Form.Item
|
|
270
|
-
name={address2Field}
|
|
271
|
-
label={getLabel(i18n, address2Field, locale)}
|
|
272
|
-
rules={getRule(address2Field)}
|
|
273
|
-
required={isRequired(address2Field)}
|
|
274
|
-
>
|
|
275
|
-
<Input placeholder={getPlaceholder(i18n, address2Field, locale)} />
|
|
276
|
-
</Form.Item>
|
|
277
|
-
|
|
278
|
-
{/* 建物名・部屋番号 */}
|
|
279
|
-
<Form.Item
|
|
280
|
-
name={address3Field}
|
|
281
|
-
label={getLabel(i18n, address3Field, locale)}
|
|
282
|
-
rules={getRule(address3Field)}
|
|
283
|
-
required={isRequired(address3Field)}
|
|
284
|
-
>
|
|
285
|
-
<Input placeholder={getPlaceholder(i18n, address3Field, locale)} />
|
|
286
|
-
</Form.Item>
|
|
287
|
-
</>
|
|
288
|
-
);
|
|
289
|
-
}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JapaneseBankField - Japanese bank account input component
|
|
3
|
-
*/
|
|
4
|
-
import { Form, Input, Select, Row, Col } from 'antd';
|
|
5
|
-
import type { FormInstance } from 'antd';
|
|
6
|
-
import type { RuleObject } from 'antd/es/form';
|
|
7
|
-
import { zodRule } from '../lib/form-validation';
|
|
8
|
-
import { getZodLocale } from '../lib/zod-i18n';
|
|
9
|
-
import { BankAccountType, BankAccountTypeValues, getBankAccountTypeLabel } from '../enum/plugin/BankAccountType';
|
|
10
|
-
|
|
11
|
-
interface I18nConfig {
|
|
12
|
-
fields: Record<string, { label?: Record<string, string>; placeholder?: Record<string, string> }>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface SelectOption {
|
|
16
|
-
value: string | number;
|
|
17
|
-
label: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface JapaneseBankFieldProps {
|
|
21
|
-
form?: FormInstance;
|
|
22
|
-
schemas: Record<string, unknown>;
|
|
23
|
-
i18n: I18nConfig;
|
|
24
|
-
prefix?: string;
|
|
25
|
-
accountTypeOptions?: SelectOption[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getLabel(i18n: I18nConfig, field: string, locale: string): string {
|
|
29
|
-
return i18n.fields[field]?.label?.[locale] ?? i18n.fields[field]?.label?.['en'] ?? field;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getPlaceholder(i18n: I18nConfig, field: string, locale: string): string {
|
|
33
|
-
return i18n.fields[field]?.placeholder?.[locale] ?? i18n.fields[field]?.placeholder?.['en'] ?? '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Build account type options from enum
|
|
37
|
-
function buildAccountTypeOptions(locale: string): SelectOption[] {
|
|
38
|
-
return BankAccountTypeValues.map(value => ({
|
|
39
|
-
value,
|
|
40
|
-
label: getBankAccountTypeLabel(value, locale),
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function JapaneseBankField({
|
|
45
|
-
schemas,
|
|
46
|
-
i18n,
|
|
47
|
-
prefix = 'bank',
|
|
48
|
-
accountTypeOptions,
|
|
49
|
-
}: JapaneseBankFieldProps) {
|
|
50
|
-
const locale = getZodLocale();
|
|
51
|
-
const options = accountTypeOptions ?? buildAccountTypeOptions(locale);
|
|
52
|
-
|
|
53
|
-
const bankCodeField = `${prefix}_bank_code`;
|
|
54
|
-
const bankNameField = `${prefix}_bank_name`;
|
|
55
|
-
const branchCodeField = `${prefix}_branch_code`;
|
|
56
|
-
const branchNameField = `${prefix}_branch_name`;
|
|
57
|
-
const accountTypeField = `${prefix}_account_type`;
|
|
58
|
-
const accountNumberField = `${prefix}_account_number`;
|
|
59
|
-
const accountHolderField = `${prefix}_account_holder`;
|
|
60
|
-
|
|
61
|
-
const getRule = (field: string): RuleObject[] => {
|
|
62
|
-
const schema = schemas[field];
|
|
63
|
-
if (!schema) return [];
|
|
64
|
-
return [zodRule(schema as any, getLabel(i18n, field, locale))];
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Check if a field is required by examining its Zod schema
|
|
68
|
-
const isRequired = (field: string): boolean => {
|
|
69
|
-
const schema = schemas[field];
|
|
70
|
-
if (!schema) return false;
|
|
71
|
-
const schemaDesc = (schema as any)?._def?.typeName;
|
|
72
|
-
const inner = (schema as any)?._def?.innerType;
|
|
73
|
-
if (schemaDesc === 'ZodOptional' || schemaDesc === 'ZodNullable') return false;
|
|
74
|
-
if (inner?._def?.typeName === 'ZodOptional' || inner?._def?.typeName === 'ZodNullable') return false;
|
|
75
|
-
return true;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const bankRequired = isRequired(bankCodeField) || isRequired(bankNameField);
|
|
79
|
-
const branchRequired = isRequired(branchCodeField) || isRequired(branchNameField);
|
|
80
|
-
|
|
81
|
-
// Short labels
|
|
82
|
-
const codeShortLabel = locale === 'ja' ? 'コード' : 'Code';
|
|
83
|
-
const nameShortLabel = locale === 'ja' ? '名称' : 'Name';
|
|
84
|
-
|
|
85
|
-
// Compound label from i18n
|
|
86
|
-
const compoundLabel = i18n.fields[prefix]?.label?.[locale]
|
|
87
|
-
?? i18n.fields[prefix]?.label?.['en']
|
|
88
|
-
?? getLabel(i18n, bankCodeField, locale);
|
|
89
|
-
|
|
90
|
-
const bankLabel = locale === 'ja' ? '銀行' : 'Bank';
|
|
91
|
-
const branchLabel = locale === 'ja' ? '支店' : 'Branch';
|
|
92
|
-
|
|
93
|
-
const prefixStyle = {
|
|
94
|
-
color: 'rgba(0, 0, 0, 0.88)',
|
|
95
|
-
fontWeight: 500,
|
|
96
|
-
borderRight: '1px solid #d9d9d9',
|
|
97
|
-
paddingRight: 8,
|
|
98
|
-
marginRight: 4,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<>
|
|
103
|
-
{/* 銀行コード・銀行名 */}
|
|
104
|
-
<Form.Item
|
|
105
|
-
label={bankLabel}
|
|
106
|
-
required={bankRequired}
|
|
107
|
-
style={{ marginBottom: 0 }}
|
|
108
|
-
>
|
|
109
|
-
<Row gutter={8}>
|
|
110
|
-
<Col span={8}>
|
|
111
|
-
<Form.Item
|
|
112
|
-
name={bankCodeField}
|
|
113
|
-
rules={getRule(bankCodeField)}
|
|
114
|
-
style={{ marginBottom: 16 }}
|
|
115
|
-
>
|
|
116
|
-
<Input
|
|
117
|
-
prefix={<span style={prefixStyle}>{codeShortLabel}</span>}
|
|
118
|
-
placeholder={getPlaceholder(i18n, bankCodeField, locale)}
|
|
119
|
-
maxLength={4}
|
|
120
|
-
/>
|
|
121
|
-
</Form.Item>
|
|
122
|
-
</Col>
|
|
123
|
-
<Col span={16}>
|
|
124
|
-
<Form.Item
|
|
125
|
-
name={bankNameField}
|
|
126
|
-
rules={getRule(bankNameField)}
|
|
127
|
-
style={{ marginBottom: 16 }}
|
|
128
|
-
>
|
|
129
|
-
<Input
|
|
130
|
-
prefix={<span style={prefixStyle}>{nameShortLabel}</span>}
|
|
131
|
-
placeholder={getPlaceholder(i18n, bankNameField, locale)}
|
|
132
|
-
/>
|
|
133
|
-
</Form.Item>
|
|
134
|
-
</Col>
|
|
135
|
-
</Row>
|
|
136
|
-
</Form.Item>
|
|
137
|
-
|
|
138
|
-
{/* 支店コード・支店名 */}
|
|
139
|
-
<Form.Item
|
|
140
|
-
label={branchLabel}
|
|
141
|
-
required={branchRequired}
|
|
142
|
-
style={{ marginBottom: 0 }}
|
|
143
|
-
>
|
|
144
|
-
<Row gutter={8}>
|
|
145
|
-
<Col span={8}>
|
|
146
|
-
<Form.Item
|
|
147
|
-
name={branchCodeField}
|
|
148
|
-
rules={getRule(branchCodeField)}
|
|
149
|
-
style={{ marginBottom: 16 }}
|
|
150
|
-
>
|
|
151
|
-
<Input
|
|
152
|
-
prefix={<span style={prefixStyle}>{codeShortLabel}</span>}
|
|
153
|
-
placeholder={getPlaceholder(i18n, branchCodeField, locale)}
|
|
154
|
-
maxLength={3}
|
|
155
|
-
/>
|
|
156
|
-
</Form.Item>
|
|
157
|
-
</Col>
|
|
158
|
-
<Col span={16}>
|
|
159
|
-
<Form.Item
|
|
160
|
-
name={branchNameField}
|
|
161
|
-
rules={getRule(branchNameField)}
|
|
162
|
-
style={{ marginBottom: 16 }}
|
|
163
|
-
>
|
|
164
|
-
<Input
|
|
165
|
-
prefix={<span style={prefixStyle}>{nameShortLabel}</span>}
|
|
166
|
-
placeholder={getPlaceholder(i18n, branchNameField, locale)}
|
|
167
|
-
/>
|
|
168
|
-
</Form.Item>
|
|
169
|
-
</Col>
|
|
170
|
-
</Row>
|
|
171
|
-
</Form.Item>
|
|
172
|
-
|
|
173
|
-
{/* 口座種別 */}
|
|
174
|
-
<Form.Item
|
|
175
|
-
name={accountTypeField}
|
|
176
|
-
label={getLabel(i18n, accountTypeField, locale)}
|
|
177
|
-
rules={getRule(accountTypeField)}
|
|
178
|
-
required={isRequired(accountTypeField)}
|
|
179
|
-
>
|
|
180
|
-
<Select
|
|
181
|
-
placeholder={getPlaceholder(i18n, accountTypeField, locale)}
|
|
182
|
-
options={options}
|
|
183
|
-
style={{ width: 150 }}
|
|
184
|
-
/>
|
|
185
|
-
</Form.Item>
|
|
186
|
-
|
|
187
|
-
{/* 口座番号 */}
|
|
188
|
-
<Form.Item
|
|
189
|
-
name={accountNumberField}
|
|
190
|
-
label={getLabel(i18n, accountNumberField, locale)}
|
|
191
|
-
rules={getRule(accountNumberField)}
|
|
192
|
-
required={isRequired(accountNumberField)}
|
|
193
|
-
>
|
|
194
|
-
<Input
|
|
195
|
-
placeholder={getPlaceholder(i18n, accountNumberField, locale)}
|
|
196
|
-
maxLength={7}
|
|
197
|
-
style={{ width: 150 }}
|
|
198
|
-
/>
|
|
199
|
-
</Form.Item>
|
|
200
|
-
|
|
201
|
-
{/* 口座名義 */}
|
|
202
|
-
<Form.Item
|
|
203
|
-
name={accountHolderField}
|
|
204
|
-
label={getLabel(i18n, accountHolderField, locale)}
|
|
205
|
-
rules={getRule(accountHolderField)}
|
|
206
|
-
required={isRequired(accountHolderField)}
|
|
207
|
-
>
|
|
208
|
-
<Input placeholder={getPlaceholder(i18n, accountHolderField, locale)} />
|
|
209
|
-
</Form.Item>
|
|
210
|
-
</>
|
|
211
|
-
);
|
|
212
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JapaneseNameField - Japanese name input component
|
|
3
|
-
* Handles lastname + firstname with optional kana fields
|
|
4
|
-
*/
|
|
5
|
-
import { Form, Input, Row, Col } from 'antd';
|
|
6
|
-
import type { FormInstance } from 'antd';
|
|
7
|
-
import type { RuleObject } from 'antd/es/form';
|
|
8
|
-
import { zodRule } from '../lib/form-validation';
|
|
9
|
-
import { getZodLocale } from '../lib/zod-i18n';
|
|
10
|
-
|
|
11
|
-
interface I18nConfig {
|
|
12
|
-
fields: Record<string, { label?: Record<string, string>; placeholder?: Record<string, string> }>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface JapaneseNameFieldProps {
|
|
16
|
-
form?: FormInstance;
|
|
17
|
-
schemas: Record<string, unknown>;
|
|
18
|
-
i18n: I18nConfig;
|
|
19
|
-
prefix?: string;
|
|
20
|
-
required?: boolean;
|
|
21
|
-
showKana?: boolean;
|
|
22
|
-
label?: string;
|
|
23
|
-
kanaLabel?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function getLabel(i18n: I18nConfig, field: string, locale: string): string {
|
|
27
|
-
return i18n.fields[field]?.label?.[locale] ?? i18n.fields[field]?.label?.['en'] ?? field;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getCompoundLabel(i18n: I18nConfig, prefix: string, locale: string): string | undefined {
|
|
31
|
-
// Try to get compound-level label (e.g., 'name' -> '氏名')
|
|
32
|
-
return i18n.fields[prefix]?.label?.[locale] ?? i18n.fields[prefix]?.label?.['en'];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getPlaceholder(i18n: I18nConfig, field: string, locale: string): string {
|
|
36
|
-
return i18n.fields[field]?.placeholder?.[locale] ?? i18n.fields[field]?.placeholder?.['en'] ?? '';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function JapaneseNameField({
|
|
40
|
-
schemas,
|
|
41
|
-
i18n,
|
|
42
|
-
prefix = 'name',
|
|
43
|
-
required = false,
|
|
44
|
-
showKana = true,
|
|
45
|
-
label,
|
|
46
|
-
kanaLabel,
|
|
47
|
-
}: JapaneseNameFieldProps) {
|
|
48
|
-
const locale = getZodLocale();
|
|
49
|
-
|
|
50
|
-
const lastnameField = `${prefix}_lastname`;
|
|
51
|
-
const firstnameField = `${prefix}_firstname`;
|
|
52
|
-
const kanaLastnameField = `${prefix}_kana_lastname`;
|
|
53
|
-
const kanaFirstnameField = `${prefix}_kana_firstname`;
|
|
54
|
-
|
|
55
|
-
const getRule = (field: string): RuleObject[] => {
|
|
56
|
-
const schema = schemas[field];
|
|
57
|
-
if (!schema) return [];
|
|
58
|
-
return [zodRule(schema as any, getLabel(i18n, field, locale))];
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Check if a field is required by examining its Zod schema
|
|
62
|
-
const isFieldRequired = (field: string): boolean => {
|
|
63
|
-
const schema = schemas[field];
|
|
64
|
-
if (!schema) return false;
|
|
65
|
-
const schemaDesc = (schema as any)?._def?.typeName;
|
|
66
|
-
const inner = (schema as any)?._def?.innerType;
|
|
67
|
-
if (schemaDesc === 'ZodOptional' || schemaDesc === 'ZodNullable') return false;
|
|
68
|
-
if (inner?._def?.typeName === 'ZodOptional' || inner?._def?.typeName === 'ZodNullable') return false;
|
|
69
|
-
return true;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const nameRequired = isFieldRequired(lastnameField) || isFieldRequired(firstnameField) || required;
|
|
73
|
-
const kanaRequired = isFieldRequired(kanaLastnameField) || isFieldRequired(kanaFirstnameField);
|
|
74
|
-
|
|
75
|
-
// Try compound label first, then fallback to first field's label
|
|
76
|
-
const nameLabel = label ?? getCompoundLabel(i18n, prefix, locale) ?? getLabel(i18n, lastnameField, locale);
|
|
77
|
-
const nameKanaLabel = kanaLabel ?? `${getCompoundLabel(i18n, prefix, locale) ?? getLabel(i18n, kanaLastnameField, locale)}(カナ)`;
|
|
78
|
-
|
|
79
|
-
// Get short field labels (姓, 名, etc.)
|
|
80
|
-
const lastnameShortLabel = locale === 'ja' ? '姓' : 'Last';
|
|
81
|
-
const firstnameShortLabel = locale === 'ja' ? '名' : 'First';
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<>
|
|
85
|
-
<Form.Item
|
|
86
|
-
label={nameLabel}
|
|
87
|
-
required={nameRequired}
|
|
88
|
-
style={{ marginBottom: 0 }}
|
|
89
|
-
>
|
|
90
|
-
<Row gutter={8}>
|
|
91
|
-
<Col span={12}>
|
|
92
|
-
<Form.Item
|
|
93
|
-
name={lastnameField}
|
|
94
|
-
rules={getRule(lastnameField)}
|
|
95
|
-
style={{ marginBottom: 16 }}
|
|
96
|
-
>
|
|
97
|
-
<Input
|
|
98
|
-
prefix={
|
|
99
|
-
<span style={{
|
|
100
|
-
color: 'rgba(0, 0, 0, 0.88)',
|
|
101
|
-
fontWeight: 500,
|
|
102
|
-
borderRight: '1px solid #d9d9d9',
|
|
103
|
-
paddingRight: 8,
|
|
104
|
-
marginRight: 4,
|
|
105
|
-
}}>
|
|
106
|
-
{lastnameShortLabel}
|
|
107
|
-
</span>
|
|
108
|
-
}
|
|
109
|
-
placeholder={getPlaceholder(i18n, lastnameField, locale)}
|
|
110
|
-
/>
|
|
111
|
-
</Form.Item>
|
|
112
|
-
</Col>
|
|
113
|
-
<Col span={12}>
|
|
114
|
-
<Form.Item
|
|
115
|
-
name={firstnameField}
|
|
116
|
-
rules={getRule(firstnameField)}
|
|
117
|
-
style={{ marginBottom: 16 }}
|
|
118
|
-
>
|
|
119
|
-
<Input
|
|
120
|
-
prefix={
|
|
121
|
-
<span style={{
|
|
122
|
-
color: 'rgba(0, 0, 0, 0.88)',
|
|
123
|
-
fontWeight: 500,
|
|
124
|
-
borderRight: '1px solid #d9d9d9',
|
|
125
|
-
paddingRight: 8,
|
|
126
|
-
marginRight: 4,
|
|
127
|
-
}}>
|
|
128
|
-
{firstnameShortLabel}
|
|
129
|
-
</span>
|
|
130
|
-
}
|
|
131
|
-
placeholder={getPlaceholder(i18n, firstnameField, locale)}
|
|
132
|
-
/>
|
|
133
|
-
</Form.Item>
|
|
134
|
-
</Col>
|
|
135
|
-
</Row>
|
|
136
|
-
</Form.Item>
|
|
137
|
-
|
|
138
|
-
{showKana && (
|
|
139
|
-
<Form.Item
|
|
140
|
-
label={nameKanaLabel}
|
|
141
|
-
required={kanaRequired}
|
|
142
|
-
style={{ marginBottom: 0 }}
|
|
143
|
-
>
|
|
144
|
-
<Row gutter={8}>
|
|
145
|
-
<Col span={12}>
|
|
146
|
-
<Form.Item
|
|
147
|
-
name={kanaLastnameField}
|
|
148
|
-
rules={getRule(kanaLastnameField)}
|
|
149
|
-
style={{ marginBottom: 16 }}
|
|
150
|
-
>
|
|
151
|
-
<Input
|
|
152
|
-
prefix={
|
|
153
|
-
<span style={{
|
|
154
|
-
color: 'rgba(0, 0, 0, 0.88)',
|
|
155
|
-
fontWeight: 500,
|
|
156
|
-
borderRight: '1px solid #d9d9d9',
|
|
157
|
-
paddingRight: 8,
|
|
158
|
-
marginRight: 4,
|
|
159
|
-
}}>
|
|
160
|
-
{lastnameShortLabel}
|
|
161
|
-
</span>
|
|
162
|
-
}
|
|
163
|
-
placeholder={getPlaceholder(i18n, kanaLastnameField, locale)}
|
|
164
|
-
/>
|
|
165
|
-
</Form.Item>
|
|
166
|
-
</Col>
|
|
167
|
-
<Col span={12}>
|
|
168
|
-
<Form.Item
|
|
169
|
-
name={kanaFirstnameField}
|
|
170
|
-
rules={getRule(kanaFirstnameField)}
|
|
171
|
-
style={{ marginBottom: 16 }}
|
|
172
|
-
>
|
|
173
|
-
<Input
|
|
174
|
-
prefix={
|
|
175
|
-
<span style={{
|
|
176
|
-
color: 'rgba(0, 0, 0, 0.88)',
|
|
177
|
-
fontWeight: 500,
|
|
178
|
-
borderRight: '1px solid #d9d9d9',
|
|
179
|
-
paddingRight: 8,
|
|
180
|
-
marginRight: 4,
|
|
181
|
-
}}>
|
|
182
|
-
{firstnameShortLabel}
|
|
183
|
-
</span>
|
|
184
|
-
}
|
|
185
|
-
placeholder={getPlaceholder(i18n, kanaFirstnameField, locale)}
|
|
186
|
-
/>
|
|
187
|
-
</Form.Item>
|
|
188
|
-
</Col>
|
|
189
|
-
</Row>
|
|
190
|
-
</Form.Item>
|
|
191
|
-
)}
|
|
192
|
-
</>
|
|
193
|
-
);
|
|
194
|
-
}
|