From a5045c6880b82ea54f3d2fbb2eaea116f5c37538 Mon Sep 17 00:00:00 2001 From: Zach Wang Date: Thu, 14 Mar 2024 13:56:02 +1300 Subject: [PATCH] feat: add DateTime, Birthday, Weekday, Month generators (#269) --- package-lock.json | 4 +- package.json | 2 +- scripts/addNewGenerator.ts | 6 +- .../DevTools/src/GenerateResultsPreviewer.tsx | 2 +- src/components/InputPanel/src/InputPanel.tsx | 2 +- .../src/components/DataTypeSelectModal.tsx | 8 +- .../Utils/src/OptionsDatetimePicker.tsx | 55 +++++ src/components/Utils/src/OptionsSwitch.tsx | 2 +- src/constants/enums.ts | 23 ++- src/core/formatters/CSharp/CSharp.tsx | 3 + .../formatters/CSharp/CSharpFormatterUtils.ts | 13 ++ src/core/formatters/Sql/Sql.tsx | 42 +++- src/core/formatters/Typescript/Typescript.tsx | 3 + src/core/generators/Birthday/Birthday.tsx | 143 +++++++++++++ src/core/generators/Birthday/index.ts | 14 ++ src/core/generators/DateTime/DateTime.tsx | 189 ++++++++++++++++++ src/core/generators/DateTime/index.ts | 14 ++ src/core/generators/Month/Month.tsx | 49 +++++ src/core/generators/Month/index.ts | 14 ++ src/core/generators/Number/Number.tsx | 8 +- src/core/generators/Weekday/Weekday.tsx | 47 +++++ src/core/generators/Weekday/index.ts | 14 ++ src/core/generators/index.ts | 8 + src/locale/translations/en.ts | 43 +++- src/locale/translations/jaJP.ts | 24 +++ src/locale/translations/zhCN.ts | 39 +++- src/utils/formatterUtils.ts | 20 +- src/utils/typeUtils.ts | 15 ++ 28 files changed, 766 insertions(+), 40 deletions(-) create mode 100644 src/components/Utils/src/OptionsDatetimePicker.tsx create mode 100644 src/core/generators/Birthday/Birthday.tsx create mode 100644 src/core/generators/Birthday/index.ts create mode 100644 src/core/generators/DateTime/DateTime.tsx create mode 100644 src/core/generators/DateTime/index.ts create mode 100644 src/core/generators/Month/Month.tsx create mode 100644 src/core/generators/Month/index.ts create mode 100644 src/core/generators/Weekday/Weekday.tsx create mode 100644 src/core/generators/Weekday/index.ts diff --git a/package-lock.json b/package-lock.json index 8916a54..9de3fe5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dummyi", - "version": "0.1.0", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dummyi", - "version": "0.1.0", + "version": "0.2.2", "dependencies": { "@douyinfe/semi-next": "^2.51.0", "@douyinfe/semi-ui": "^2.51.0", diff --git a/package.json b/package.json index d7ada64..e0590dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dummyi", - "version": "0.2.2", + "version": "0.2.3", "private": true, "scripts": { "dev": "next dev", diff --git a/scripts/addNewGenerator.ts b/scripts/addNewGenerator.ts index e81710c..5403898 100644 --- a/scripts/addNewGenerator.ts +++ b/scripts/addNewGenerator.ts @@ -106,7 +106,7 @@ export const ${generatorName}GeneratorDefaultOptions:${generatorName}GeneratorOp // ------------------------------------------------------------------------------------------------------------- // generate method -export const generate = (options: any): GenerateResult => { +export const generate = (options: ${generatorName}GeneratorOptions): GenerateResult => { // TODO: implement your own generate method here return { @@ -125,9 +125,9 @@ export const ${generatorName}GeneratorOptionsComponent: React.FunctionComponent< // TODO: implement your own options component here return ( -
+ <> NOT IMPLEMENTED -
+ ); }`; } diff --git a/src/components/DevTools/src/GenerateResultsPreviewer.tsx b/src/components/DevTools/src/GenerateResultsPreviewer.tsx index 595aa5a..5c90700 100644 --- a/src/components/DevTools/src/GenerateResultsPreviewer.tsx +++ b/src/components/DevTools/src/GenerateResultsPreviewer.tsx @@ -77,7 +77,7 @@ export const GenerateResultsPreviewer: React.FunctionComponent - + {formatType} {isError && ERROR} diff --git a/src/components/InputPanel/src/InputPanel.tsx b/src/components/InputPanel/src/InputPanel.tsx index 3f66bee..bdc375e 100644 --- a/src/components/InputPanel/src/InputPanel.tsx +++ b/src/components/InputPanel/src/InputPanel.tsx @@ -23,7 +23,7 @@ export const InputPanel: React.FunctionComponent = () => { setPanelHeight(containerHeight); - if (containerWidth > 1000) { + if (containerWidth > 900) { setComponentSize(ComponentSize.LARGE); } else if (containerWidth > 550) { setComponentSize(ComponentSize.MEDIUM); diff --git a/src/components/InputPanel/src/components/DataTypeSelectModal.tsx b/src/components/InputPanel/src/components/DataTypeSelectModal.tsx index 72a0037..2200c79 100644 --- a/src/components/InputPanel/src/components/DataTypeSelectModal.tsx +++ b/src/components/InputPanel/src/components/DataTypeSelectModal.tsx @@ -21,7 +21,7 @@ export interface DataTypeSelectModalProps { export const DataTypeSelectModal: React.FunctionComponent = ({...props}) => { const intl = useIntl(); - const {Title} = Typography; + const {Title, Text} = Typography; const dispatch = useDispatch(); const [searchText, setSearchText] = React.useState(null); const data = useMemo(() => getGeneratorList(searchText, intl), [intl, searchText]); @@ -34,7 +34,7 @@ export const DataTypeSelectModal: React.FunctionComponent { - dispatch(doChangeDataType(currentTargetDataFieldId,item.type)); + dispatch(doChangeDataType(currentTargetDataFieldId, item.type)); onCancel(); } @@ -106,7 +106,9 @@ export const DataTypeSelectModal: React.FunctionComponent (
- {example} + + {example} +
)) } diff --git a/src/components/Utils/src/OptionsDatetimePicker.tsx b/src/components/Utils/src/OptionsDatetimePicker.tsx new file mode 100644 index 0000000..cf6a92e --- /dev/null +++ b/src/components/Utils/src/OptionsDatetimePicker.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import {ErrorTooltip, InfoTooltip} from "@/components/Utils"; +import {useIntl} from "@/locale"; +import {isNullOrWhiteSpace} from "@/utils/stringUtils"; +import {DatePicker} from "@douyinfe/semi-ui"; +import {hasValue} from "@/utils/typeUtils"; + +export interface OptionsDatetimePickerProps { + label: string | React.ReactNode; + type?: "date" | "dateTime" | "dateRange" | "dateTimeRange"; + infoTooltip?: string | React.ReactNode; + errorMessage?: string; + value: string | number | Date | string[] | number[] | Date[]; + onChange: (value: any) => void; + style?: React.CSSProperties; + required?: boolean; +} + +export const OptionsDatetimePicker: React.FunctionComponent = ({...props}) => { + const {label, type, infoTooltip, errorMessage, value, style, onChange, required} = props; + const intl = useIntl(); + + // Add a new useState to manage the validation error message + const [validationError, setValidationError] = React.useState(); + + // Add effect to validate value when it changes or when required status changes + React.useEffect(() => { + if (required && !hasValue(value)) { + setValidationError(intl.formatMessage({id: 'error.input.isRequired'})); // Set default required error message or use props.errorMessage + } else { + setValidationError(undefined); // Clear error message when input is valid + } + }, [value, required]); + + return ( +
+
+ {label} + {infoTooltip && + {infoTooltip} + } +
+ + onChange(dateString)} + value={value} + style={style} + validateStatus={!isNullOrWhiteSpace(validationError || errorMessage) ? 'error' : 'default'} + /> + +
+ ) +} \ No newline at end of file diff --git a/src/components/Utils/src/OptionsSwitch.tsx b/src/components/Utils/src/OptionsSwitch.tsx index 05eb65d..9c4acd7 100644 --- a/src/components/Utils/src/OptionsSwitch.tsx +++ b/src/components/Utils/src/OptionsSwitch.tsx @@ -27,7 +27,7 @@ export const OptionsSwitch: React.FunctionComponent = ({...p diff --git a/src/constants/enums.ts b/src/constants/enums.ts index 918d080..f0a0ab6 100644 --- a/src/constants/enums.ts +++ b/src/constants/enums.ts @@ -1,4 +1,3 @@ - // export format export enum ExportFormatCategory { FILE_TYPES = "file_types", @@ -18,17 +17,18 @@ export enum ExportFormat { export enum ValueType { STRING = "string", - TEXT= "text", + TEXT = "text", ONE_BIT = "1bit", INT = "integer", BIGINT = "bigint", DOUBLE = "double", BOOLEAN = "boolean", INT_LIST = "int_list", - STRING_LIST = "string_list" + STRING_LIST = "string_list", + DATE_TIME = "date_time" } -export enum ExportProcessStage{ +export enum ExportProcessStage { PREVIEW = "preview", GENERATING = "generating", COMPLETED = "completed", @@ -36,14 +36,19 @@ export enum ExportProcessStage{ // data types export enum DataTypeCategory { - ALL= "all", + ALL = "all", BASIC = "basic", PERSON = "person", NETWORK = "network", COMMERCE = "commerce", + DATETIME = "datetime" } export enum DataType { + BIRTHDAY = "birthday", + MONTH = "month", + WEEKDAY = "weekday", + DATETIME = "datetime", URL = "url", DOMAINSUFFIX = "domainsuffix", DOMAINNAME = "domainname", @@ -81,18 +86,18 @@ export enum Locales { JA_JP = "ja-JP", } -export enum ComponentSize{ +export enum ComponentSize { SMALL = "small", MEDIUM = "medium", LARGE = "large", } -export enum PreviewType{ +export enum PreviewType { TABLE = "table", RAW = "raw" } -export enum CollectionNodeType{ +export enum CollectionNodeType { COLLECTION = "collection", SCHEMA = "schema" } @@ -102,7 +107,7 @@ export enum EndOfLineChars { CRLF = '\r\n' } -export enum Sex{ +export enum Sex { ALL = "all", MALE = "male", FEMALE = "female" diff --git a/src/core/formatters/CSharp/CSharp.tsx b/src/core/formatters/CSharp/CSharp.tsx index cff70b9..ed4d198 100644 --- a/src/core/formatters/CSharp/CSharp.tsx +++ b/src/core/formatters/CSharp/CSharp.tsx @@ -72,6 +72,9 @@ export const format = (request: FormatRequest): string => { case ValueType.ONE_BIT: fieldType = `int${field.emptyRate !== 0 ? "?" : ""}`; break; + case ValueType.DATE_TIME: + fieldType = `DateTime${field.emptyRate !== 0 ? "?" : ""}` + break; // Add more cases as necessary } csharpCode += ` public ${fieldType} ${field.fieldName} { get; set; }\n`; diff --git a/src/core/formatters/CSharp/CSharpFormatterUtils.ts b/src/core/formatters/CSharp/CSharpFormatterUtils.ts index f3cfed2..8eea719 100644 --- a/src/core/formatters/CSharp/CSharpFormatterUtils.ts +++ b/src/core/formatters/CSharp/CSharpFormatterUtils.ts @@ -26,7 +26,20 @@ export function formatValueForCSharp(generateResult: GenerateResult, valueType: return `new List { ${value.join(", ")} }` case ValueType.STRING_LIST: return `new List { ${value.map(item => `"${item}"`).join(', ')})} }` + case ValueType.DATE_TIME: + return `DateTime.Parse("${toCSharpDateTimeFormat(value)}")` default: return 'null'; // Or some other default case } +} + +export function toCSharpDateTimeFormat(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份是从0开始的 + const day = String(date.getDate()).padStart(2, '0'); + + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; } \ No newline at end of file diff --git a/src/core/formatters/Sql/Sql.tsx b/src/core/formatters/Sql/Sql.tsx index 161922c..86d10df 100644 --- a/src/core/formatters/Sql/Sql.tsx +++ b/src/core/formatters/Sql/Sql.tsx @@ -61,14 +61,16 @@ const addCreateTableColumn = (field: DataField, sqlType: SqlType) => { fieldType = (sqlType === SqlType.ORACLE || sqlType === SqlType.IBMDB2) ? "CLOB" : "TEXT"; break; case ValueType.ONE_BIT: - fieldType = "TINYINT(1)"; - break; case ValueType.BOOLEAN: fieldType = "TINYINT(1)"; break; case ValueType.BIGINT: fieldType = "BIGINT"; break; + case ValueType.DATE_TIME: // Add this line + // Define default DATETIME format, then override per SQL type if necessary + fieldType = "DATETIME"; + break; default: fieldType = "VARCHAR(255)"; break; @@ -77,23 +79,29 @@ const addCreateTableColumn = (field: DataField, sqlType: SqlType) => { // Apply SQL type-specific modifications switch (sqlType) { case SqlType.ORACLE: - // Oracle-specific adaptations, e.g., use NUMBER instead of INT + // Oracle-specific adaptations if (fieldType === "INT") { fieldType = "NUMBER"; } else if (fieldType === "TINYINT(1)") { fieldType = "NUMBER(1)"; + } else if (fieldType === "DATETIME") { // Add this line + fieldType = "TIMESTAMP"; // Or DATE, depending on your needs } break; case SqlType.POSTGRES: - // Postgres-specific adaptations, e.g., use BOOLEAN instead of TINYINT(1) + // Postgres-specific adaptations if (fieldType === "TINYINT(1)") { fieldType = "BOOLEAN"; + } else if (fieldType === "DATETIME") { // Add this line + fieldType = "TIMESTAMP"; // Or TIMESTAMP WITHOUT TIME ZONE, depending on your needs } break; case SqlType.SQLITE: - // SQLite uses a more dynamic type system + // SQLite adaptations if (fieldType === "TINYINT(1)") { fieldType = "INTEGER"; + } else if (fieldType === "DATETIME") { // Add this line + fieldType = "TEXT"; // SQLite uses TEXT for date and time types } break; // Add cases for other SQL types as necessary @@ -116,7 +124,29 @@ const formatValueForSQL = (value: any, sqlType: SqlType, valueType: ValueType): case ValueType.INT_LIST: return `'${value.join(", ")}'` case ValueType.STRING_LIST: - return `'${value.map(item => `"${item}"`).join(', ')}'` + return `'${value.map((item: string): string => `"${item}"`).join(', ')}'` + case ValueType.DATE_TIME: + let formattedDate: string; + // Assuming value is a JavaScript Date object for simplicity + const date = (value instanceof Date) ? value : new Date(value); + + switch (sqlType) { + case SqlType.ORACLE: + // Oracle format: 'YYYY-MM-DD HH24:MI:SS' + formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`; + break; + case SqlType.POSTGRES: + // PostgreSQL format: 'YYYY-MM-DD HH:MM:SS' + formattedDate = date.toISOString().slice(0, 19).replace('T', ' '); + break; + // Add additional cases for different SQL types as needed + default: + // Default to ISO format 'YYYY-MM-DD HH:MM:SS' + formattedDate = date.toISOString().slice(0, 19).replace('T', ' '); + break; + } + + return `'${formattedDate}'`; default: return value; } diff --git a/src/core/formatters/Typescript/Typescript.tsx b/src/core/formatters/Typescript/Typescript.tsx index 5d5f286..7a645d1 100644 --- a/src/core/formatters/Typescript/Typescript.tsx +++ b/src/core/formatters/Typescript/Typescript.tsx @@ -63,6 +63,9 @@ export const format = (request: FormatRequest): string => { case ValueType.STRING_LIST: fieldType = 'string[]' break; + case ValueType.DATE_TIME: + fieldType = "Date" + break; } output += ` ${field.fieldName}${field.emptyRate !== 0 ? "?" : ""}: ${fieldType};\n`; }); diff --git a/src/core/generators/Birthday/Birthday.tsx b/src/core/generators/Birthday/Birthday.tsx new file mode 100644 index 0000000..d7912d1 --- /dev/null +++ b/src/core/generators/Birthday/Birthday.tsx @@ -0,0 +1,143 @@ +import React from "react"; +import {GenerateResult, GeneratorOptionsComponentInterface} from "@/types/generator"; +import {DateTimeFormat, DateTimeFormatSelectOptions, formatDateTime} from "@/core/generators/DateTime/DateTime"; +import {OptionsNumberInput, OptionsSelect, SelectOption} from "@/components/Utils"; +import {FormattedMessage} from "@/locale"; +import {faker} from "@faker-js/faker"; +import {ValueType} from "@/constants/enums"; + +// ------------------------------------------------------------------------------------------------------------- +// types + +export enum BirthdayGeneratorMode { + AGE = "age", + YEAR = "year" +} + +export interface BirthdayGeneratorOptions { + format: DateTimeFormat; + mode: BirthdayGeneratorMode; + minAge: number; + maxAge: number; + fromYear: number; + toYear: number; +} + +// ------------------------------------------------------------------------------------------------------------- +// default options +export const BirthdayGeneratorDefaultOptions: BirthdayGeneratorOptions = { + format: DateTimeFormat.DATETIME, + mode: BirthdayGeneratorMode.AGE, + minAge: 1, + maxAge: 80, + fromYear: 1960, + toYear: 2000, +} + +// ------------------------------------------------------------------------------------------------------------- +// generate method +export const generate = (options: BirthdayGeneratorOptions): GenerateResult => { + + const value = faker.date.birthdate({ + mode: options.mode, + min: options.mode === BirthdayGeneratorMode.AGE ? options.minAge : options.fromYear, + max: options.mode === BirthdayGeneratorMode.AGE ? options.maxAge : options.toYear, + refDate: Date.now() + }) + const output = formatDateTime(value, options.format); + + return { + value: output, + stringValue: (output instanceof Date) ? output.toISOString() : output.toString() + }; +} + +// ------------------------------------------------------------------------------------------------------------- +// options component +export const BirthdayGeneratorOptionsComponent: React.FunctionComponent = ({...props}) => { + const {options, handleOptionValueChange} = props as { + options: BirthdayGeneratorOptions, + handleOptionValueChange: typeof props.handleOptionValueChange + }; + + const handleChangeFormat = (format: DateTimeFormat) => { + if (format === DateTimeFormat.TIMESTAMP) { + handleOptionValueChange("format", format, ValueType.INT); + } else if (format === DateTimeFormat.TEXT) { + handleOptionValueChange("format", format, ValueType.STRING); + } else if (format === DateTimeFormat.DATETIME) { + handleOptionValueChange("format", format, ValueType.DATE_TIME); + } + } + + return ( + <> + } + selectOptions={BirthdayGeneratorModeSelectOptions} + value={options.mode} + onChange={(v) => handleOptionValueChange("mode", v)} + /> + + {options.mode === BirthdayGeneratorMode.AGE && <> + } + value={options.minAge} + onChange={(v) => handleOptionValueChange("minAge", v)} + style={{width: "70px"}} + min={0} + max={options.maxAge} + /> + + } + value={options.maxAge} + onChange={(v) => handleOptionValueChange("maxAge", v)} + style={{width: "70px"}} + min={options.minAge} + max={150} + /> + } + + + {options.mode === BirthdayGeneratorMode.YEAR && <> + } + value={options.fromYear} + onChange={(v) => handleOptionValueChange("fromYear", v)} + style={{width: "90px"}} + min={1000} + max={options.toYear} + /> + + } + value={options.toYear} + onChange={(v) => handleOptionValueChange("toYear", v)} + style={{width: "100px"}} + min={options.toYear} + max={2500} + /> + } + + } + selectOptions={DateTimeFormatSelectOptions} + value={options.format} + onChange={handleChangeFormat} + style={{width: "200px"}} + /> + + + ); +} + +const BirthdayGeneratorModeSelectOptions: SelectOption[] = [ + { + label: , + value: BirthdayGeneratorMode.AGE + }, { + label: , + value: BirthdayGeneratorMode.YEAR + } +] \ No newline at end of file diff --git a/src/core/generators/Birthday/index.ts b/src/core/generators/Birthday/index.ts new file mode 100644 index 0000000..f915ab9 --- /dev/null +++ b/src/core/generators/Birthday/index.ts @@ -0,0 +1,14 @@ +import {Generator} from "@/types/generator"; +import {DataType, DataTypeCategory, ValueType} from "@/constants/enums"; +import {BirthdayGeneratorDefaultOptions, BirthdayGeneratorOptionsComponent, generate} from "./Birthday"; + +export const BirthdayGenerator: Generator = { + type: DataType.BIRTHDAY, + category: DataTypeCategory.PERSON, + generate: generate, + optionsComponent: BirthdayGeneratorOptionsComponent, + defaultOptions: BirthdayGeneratorDefaultOptions, + defaultValueType: ValueType.DATE_TIME, + exampleLines: ["1949-08-11T20:00:14.636Z", "-640186506999", "18-02-1968 20:14:50"] +} + \ No newline at end of file diff --git a/src/core/generators/DateTime/DateTime.tsx b/src/core/generators/DateTime/DateTime.tsx new file mode 100644 index 0000000..ed66fe4 --- /dev/null +++ b/src/core/generators/DateTime/DateTime.tsx @@ -0,0 +1,189 @@ +import React from "react"; +import {GenerateResult, GeneratorOptionsComponentInterface} from "@/types/generator"; +import {OptionsSelect, SelectOption} from "@/components/Utils"; +import {FormattedMessage} from "@/locale"; +import {OptionsDatetimePicker} from "@/components/Utils/src/OptionsDatetimePicker"; +import {faker} from "@faker-js/faker"; +import {ValueType} from "@/constants/enums"; +import style from "@/core/generators/Boolean/Boolean.module.scss"; +import {Tag} from "@douyinfe/semi-ui"; +import {toDateTimeString} from "@/utils/typeUtils"; + +// ------------------------------------------------------------------------------------------------------------- +// types +export enum DateTimeTerms { + ANYTIME = "anytime", + BETWEEN = "between", + FUTURE = "future", + PAST = "past", + RECENT = "recent", + SOON = "soon" +} + +export enum DateTimeFormat { + TEXT = "text", + TIMESTAMP = "timestamp", + DATETIME = "datetime" +} + +export interface DateTimeGeneratorOptions { + terms: DateTimeTerms; + ref: Date | number | string; + format: DateTimeFormat; + timeRange: Date[] | number[] | string[]; +} + +// ------------------------------------------------------------------------------------------------------------- +// default options +export const DateTimeGeneratorDefaultOptions: DateTimeGeneratorOptions = { + terms: DateTimeTerms.ANYTIME, + ref: Date.now(), + format: DateTimeFormat.DATETIME, + timeRange: [new Date(2023, 0, 1), new Date()] +} + +// ------------------------------------------------------------------------------------------------------------- +// generate method +export const generate = (options: DateTimeGeneratorOptions): GenerateResult => { + const {terms, ref, format} = options; + + let value: Date; + const fakerOptions = {refDate: ref} + + switch (terms) { + case DateTimeTerms.FUTURE: + value = faker.date.future(fakerOptions); + break; + case DateTimeTerms.PAST: + value = faker.date.past(fakerOptions); + break; + case DateTimeTerms.RECENT: + value = faker.date.recent(fakerOptions); + break; + case DateTimeTerms.SOON: + value = faker.date.soon(fakerOptions); + break; + case DateTimeTerms.BETWEEN: + value = faker.date.between({from: options.timeRange[0], to: options.timeRange[1]}); + break; + default: + value = faker.date.anytime(fakerOptions); + } + + const output = formatDateTime(value, format); + + return { + value: output, + stringValue: (output instanceof Date) ? output.toISOString() : output.toString() + }; +} + +// ------------------------------------------------------------------------------------------------------------- +// options component +export const DateTimeGeneratorOptionsComponent: React.FunctionComponent = ({...props}) => { + + const {options, handleOptionValueChange} = props as { + options: DateTimeGeneratorOptions, + handleOptionValueChange: typeof props.handleOptionValueChange + }; + + const handleChangeFormat = (format: DateTimeFormat) => { + if (format === DateTimeFormat.TIMESTAMP) { + handleOptionValueChange("format", format, ValueType.INT); + } else if (format === DateTimeFormat.TEXT) { + handleOptionValueChange("format", format, ValueType.STRING); + } else if (format === DateTimeFormat.DATETIME) { + handleOptionValueChange("format", format, ValueType.DATE_TIME); + } + } + + return ( + <> + } + selectOptions={DateTimeTermsSelectOptions} + value={options.terms} + onChange={(v) => handleOptionValueChange("terms", v)} + style={{width: "130px"}} + /> + + { + options.terms === DateTimeTerms.BETWEEN ? } + type={'dateTimeRange'} + value={options.timeRange} + onChange={(v) => handleOptionValueChange("timeRange", v)} + style={{width: "260px"}} + required + /> + : + } + infoTooltip={} + type={'dateTime'} + value={options.ref} + onChange={(v) => handleOptionValueChange("ref", v)} + style={{width: "185px"}} + required + /> + } + + } + selectOptions={DateTimeFormatSelectOptions} + value={options.format} + onChange={handleChangeFormat} + style={{width: "200px"}} + /> + + ); +} + +export const formatDateTime = (date: Date, format: DateTimeFormat) => { + switch (format) { + case DateTimeFormat.DATETIME: + return date; + case DateTimeFormat.TIMESTAMP: + return date.getTime(); + case DateTimeFormat.TEXT: + return toDateTimeString(date); + } +} + +export const DateTimeTermsSelectOptions: SelectOption[] = Object.values(DateTimeTerms).map((term) => { + return { + value: term, + label: + }; +}) + +export const DateTimeFormatSelectOptions: SelectOption[] = [ + { + value: DateTimeFormat.DATETIME, + label: + <> + Date + + + }, + { + value: DateTimeFormat.TIMESTAMP, + label: + <> + number + + + }, + { + value: DateTimeFormat.TEXT, + label: + <> + string + + + }, + +] \ No newline at end of file diff --git a/src/core/generators/DateTime/index.ts b/src/core/generators/DateTime/index.ts new file mode 100644 index 0000000..1251e07 --- /dev/null +++ b/src/core/generators/DateTime/index.ts @@ -0,0 +1,14 @@ +import {Generator} from "@/types/generator"; +import {DataType, DataTypeCategory, ValueType} from "@/constants/enums"; +import {DateTimeGeneratorDefaultOptions, DateTimeGeneratorOptionsComponent, generate} from "./DateTime"; + +export const DateTimeGenerator: Generator = { + type: DataType.DATETIME, + category: DataTypeCategory.DATETIME, + generate: generate, + optionsComponent: DateTimeGeneratorOptionsComponent, + defaultOptions: DateTimeGeneratorDefaultOptions, + defaultValueType: ValueType.DATE_TIME, + exampleLines: ["1725842513046", "29-01-2025 20:15:40", "13-06-2023 08:16:09"] +} + \ No newline at end of file diff --git a/src/core/generators/Month/Month.tsx b/src/core/generators/Month/Month.tsx new file mode 100644 index 0000000..314b1b1 --- /dev/null +++ b/src/core/generators/Month/Month.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import {GenerateResult, GeneratorOptionsComponentInterface} from "@/types/generator"; +import {OptionsSwitch} from "@/components/Utils/src/OptionsSwitch"; +import {FormattedMessage} from "@/locale"; +import {faker} from "@faker-js/faker"; + +// ------------------------------------------------------------------------------------------------------------- +// types +export interface MonthGeneratorOptions { + abbreviated: boolean; +} + +// ------------------------------------------------------------------------------------------------------------- +// default options +export const MonthGeneratorDefaultOptions: MonthGeneratorOptions = { + abbreviated: false +} + +// ------------------------------------------------------------------------------------------------------------- +// generate method +export const generate = (options: MonthGeneratorOptions): GenerateResult => { + const {abbreviated} = options; + const value = faker.date.month({abbreviated:abbreviated}) + return { + value: value, + stringValue: value + }; +} + +// ------------------------------------------------------------------------------------------------------------- +// options component +export const MonthGeneratorOptionsComponent: React.FunctionComponent = ({...props}) => { + const {options, handleOptionValueChange} = props as { + options: MonthGeneratorOptions, + handleOptionValueChange: typeof props.handleOptionValueChange + }; + + // TODO: implement your own options component here + return ( + <> + } + value={options.abbreviated} + onChange={(v) => handleOptionValueChange("abbreviated", v)} + /> + + + ); +} \ No newline at end of file diff --git a/src/core/generators/Month/index.ts b/src/core/generators/Month/index.ts new file mode 100644 index 0000000..825940f --- /dev/null +++ b/src/core/generators/Month/index.ts @@ -0,0 +1,14 @@ +import {Generator} from "@/types/generator"; +import {DataType, DataTypeCategory, ValueType} from "@/constants/enums"; +import {MonthGeneratorDefaultOptions, MonthGeneratorOptionsComponent, generate} from "./Month"; + +export const MonthGenerator: Generator = { + type: DataType.MONTH, + category: DataTypeCategory.DATETIME, + generate: generate, + optionsComponent: MonthGeneratorOptionsComponent, + defaultOptions: MonthGeneratorDefaultOptions, + defaultValueType: ValueType.STRING, + exampleLines: ["September", "Feb", "December"] +} + \ No newline at end of file diff --git a/src/core/generators/Number/Number.tsx b/src/core/generators/Number/Number.tsx index 29a6119..f975519 100644 --- a/src/core/generators/Number/Number.tsx +++ b/src/core/generators/Number/Number.tsx @@ -95,18 +95,14 @@ export const NumberGeneratorOptionsComponent: React.FunctionComponent { const newErrorMessages = {...errorMessages}; // min - if (isNullOrWhiteSpace(options.min.toString())) { - newErrorMessages.min = intl.formatMessage({id: 'dataType.number.min.errorMessage.empty'}) - } else if (options.min > options.max) { + if (options.min > options.max) { newErrorMessages.min = intl.formatMessage({id: 'dataType.number.min.errorMessage.greaterThanMax'}) } else { newErrorMessages.min = ''; } // max - if (isNullOrWhiteSpace(options.max.toString())) { - newErrorMessages.max = intl.formatMessage({id: 'dataType.number.max.errorMessage.empty'}) - } else if (options.max < options.min) { + if (options.max < options.min) { newErrorMessages.max = intl.formatMessage({id: 'dataType.number.max.errorMessage.lessThanMin'}) } else { newErrorMessages.max = ''; diff --git a/src/core/generators/Weekday/Weekday.tsx b/src/core/generators/Weekday/Weekday.tsx new file mode 100644 index 0000000..a9ed699 --- /dev/null +++ b/src/core/generators/Weekday/Weekday.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import {GenerateResult, GeneratorOptionsComponentInterface} from "@/types/generator"; +import {OptionsSwitch} from "@/components/Utils/src/OptionsSwitch"; +import {FormattedMessage} from "@/locale"; +import {faker} from "@faker-js/faker"; + +// ------------------------------------------------------------------------------------------------------------- +// types +export interface WeekdayGeneratorOptions { + abbreviated: boolean; +} + +// ------------------------------------------------------------------------------------------------------------- +// default options +export const WeekdayGeneratorDefaultOptions: WeekdayGeneratorOptions = { + abbreviated: false +} + +// ------------------------------------------------------------------------------------------------------------- +// generate method +export const generate = (options: WeekdayGeneratorOptions): GenerateResult => { + const {abbreviated} = options; + const value = faker.date.weekday({abbreviated: abbreviated}) + return { + value: value, + stringValue: value + }; +} + +// ------------------------------------------------------------------------------------------------------------- +// options component +export const WeekdayGeneratorOptionsComponent: React.FunctionComponent = ({...props}) => { + const {options, handleOptionValueChange} = props as { + options: WeekdayGeneratorOptions, + handleOptionValueChange: typeof props.handleOptionValueChange + }; + + return ( + <> + } + value={options.abbreviated} + onChange={(v) => handleOptionValueChange("abbreviated", v)} + /> + + ); +} \ No newline at end of file diff --git a/src/core/generators/Weekday/index.ts b/src/core/generators/Weekday/index.ts new file mode 100644 index 0000000..8fe15f1 --- /dev/null +++ b/src/core/generators/Weekday/index.ts @@ -0,0 +1,14 @@ +import {Generator} from "@/types/generator"; +import {DataType, DataTypeCategory, ValueType} from "@/constants/enums"; +import {WeekdayGeneratorDefaultOptions, WeekdayGeneratorOptionsComponent, generate} from "./Weekday"; + +export const WeekdayGenerator: Generator = { + type: DataType.WEEKDAY, + category: DataTypeCategory.DATETIME, + generate: generate, + optionsComponent: WeekdayGeneratorOptionsComponent, + defaultOptions: WeekdayGeneratorDefaultOptions, + defaultValueType: ValueType.STRING, + exampleLines: ["Wednesday", "Wed", "Thursday"] +} + \ No newline at end of file diff --git a/src/core/generators/index.ts b/src/core/generators/index.ts index 8d6ffd1..7d98b22 100644 --- a/src/core/generators/index.ts +++ b/src/core/generators/index.ts @@ -1,3 +1,7 @@ +import {BirthdayGenerator} from "@/core/generators/Birthday"; +import {MonthGenerator} from "@/core/generators/Month"; +import {WeekdayGenerator} from "@/core/generators/Weekday"; +import {DateTimeGenerator} from "@/core/generators/DateTime"; import {UrlGenerator} from "@/core/generators/Url"; import {DomainSuffixGenerator} from "@/core/generators/DomainSuffix"; import {DomainNameGenerator} from "@/core/generators/DomainName"; @@ -19,6 +23,10 @@ import {CompanyNameGenerator} from "@/core/generators/CompanyName"; import {DataType} from "@/constants/enums"; export const generators = { + [DataType.BIRTHDAY]: BirthdayGenerator, + [DataType.MONTH]: MonthGenerator, + [DataType.WEEKDAY]: WeekdayGenerator, + [DataType.DATETIME]: DateTimeGenerator, [DataType.URL]: UrlGenerator, [DataType.DOMAINSUFFIX]: DomainSuffixGenerator, [DataType.DOMAINNAME]: DomainNameGenerator, diff --git a/src/locale/translations/en.ts b/src/locale/translations/en.ts index bcbef4e..a298c96 100644 --- a/src/locale/translations/en.ts +++ b/src/locale/translations/en.ts @@ -86,6 +86,45 @@ export const en = { // data types + + // birthday + "dataType.birthday": "Birthday", + "dataType.birthday.mode":"Mode", + "dataType.birthday.mode.age":"Age", + "dataType.birthday.mode.year":"Year", + "dataType.birthday.mode.minAge":"Min. Age", + "dataType.birthday.mode.maxAge":"Max. Age", + "dataType.birthday.mode.fromYear":"From year", + "dataType.birthday.mode.toYear":"To year", + + // month + "dataType.month": "Month", + "dataType.month.abbreviated": "Abbreviated", + + // weekday + "dataType.weekday": "Weekday", + "dataType.weekday.abbreviated": "Abbreviated", + + // datetime + "dataType.datetime": "DateTime", + "dataType.datetime.terms": "Terms", + "dataType.datetime.terms.anytime": "Anytime", + "dataType.datetime.terms.between": "Between", + "dataType.datetime.terms.future": "Future", + "dataType.datetime.terms.past": "Past", + "dataType.datetime.terms.recent": "Recent past", + "dataType.datetime.terms.soon": "Soon", + "dataType.datetime.ref": "Reference date", + "dataType.datetime.ref.tooltip": "The date to use as reference point", + "dataType.datetime.format": "Format", + "dataType.datetime.format.text": "Text", + "dataType.datetime.format.timestamp": "Timestamp", + "dataType.datetime.format.datetime": "DateTime", + "dataType.datetime.timeRange": "Range", + + // anytime + "dataType.anytime": "Anytime", + // url "dataType.url": "Url", "dataType.url.appendSlash.label": "Slash", @@ -159,9 +198,6 @@ export const en = { "dataType.number.max.errorMessage.empty": "Max. value cannot be empty", "dataType.number.max.errorMessage.lessThanMin": "Max. value cannot be less than min. value", - // dateTime - "dataType.dateTime": "Date time", - // boolean "dataType.boolean": "Boolean", "dataType.boolean.true.label": "Prob of True", @@ -195,6 +231,7 @@ export const en = { "dataType.category.person": "Person", "dataType.category.commerce": "Commerce", "dataType.category.network": "Network", + "dataType.category.datetime": "Datetime", // ------------------------------------------------------------------------------------------------------------- // pages diff --git a/src/locale/translations/jaJP.ts b/src/locale/translations/jaJP.ts index 5a73c9f..4800825 100644 --- a/src/locale/translations/jaJP.ts +++ b/src/locale/translations/jaJP.ts @@ -86,6 +86,30 @@ export const jaJP = { // data types + + + + + + + // birthday + "dataType.birthday": "Birthday", + + // month + "dataType.month": "Month", + + // weekday + "dataType.weekday": "Weekday", + + // datetimerange + "dataType.datetimerange": "DateTimeRange", + + // datetime + "dataType.datetime": "DateTime", + + // anytime + "dataType.anytime": "Anytime", + // url "dataType.url": "URL", "dataType.url.appendSlash.label": "スラッシュ", diff --git a/src/locale/translations/zhCN.ts b/src/locale/translations/zhCN.ts index 2cdb78d..7438930 100644 --- a/src/locale/translations/zhCN.ts +++ b/src/locale/translations/zhCN.ts @@ -83,6 +83,41 @@ export const zhCN = { // ------------------------------------------------------------------------------------------------------------- // data types + + // birthday + "dataType.birthday": "生日", + "dataType.birthday.mode":"模式", + "dataType.birthday.mode.age":"年龄", + "dataType.birthday.mode.year":"出生年份", + "dataType.birthday.mode.minAge":"最小年龄", + "dataType.birthday.mode.maxAge":"最大年龄", + "dataType.birthday.mode.fromYear":"最小年份", + "dataType.birthday.mode.toYear":"最大年份", + + // month + "dataType.month": "月份", + "dataType.month.abbreviated": "缩写", + + // weekday + "dataType.weekday": "星期", + "dataType.weekday.abbreviated": "缩写", + + // datetime + "dataType.datetime": "日期时间", + "dataType.datetime.terms": "条件", + "dataType.datetime.terms.anytime": "任意时间", + "dataType.datetime.terms.between": "区间", + "dataType.datetime.terms.future": "未来", + "dataType.datetime.terms.past": "过去", + "dataType.datetime.terms.recent": "不久之前", + "dataType.datetime.terms.soon": "不久之后", + "dataType.datetime.ref": "参考日期", + "dataType.datetime.ref.tooltip": "用于生成数据的参考日期", + "dataType.datetime.format": "格式", + "dataType.datetime.format.text": "文本", + "dataType.datetime.format.timestamp": "时间戳", + "dataType.datetime.format.datetime": "Date对象", + "dataType.datetime.timeRange": "区间", // url "dataType.url": "网址", @@ -157,9 +192,6 @@ export const zhCN = { "dataType.number.max.errorMessage.empty": "最大值不能为空", "dataType.number.max.errorMessage.lessThanMin": "最大值不能小于最小值", - // dateTime - "dataType.dateTime": "日期时间", - // boolean "dataType.boolean": "布尔值", "dataType.boolean.true.label": "真值概率", @@ -193,6 +225,7 @@ export const zhCN = { "dataType.category.person": "人物", "dataType.category.commerce": "商业", "dataType.category.network": "网络", + "dataType.category.datetime": "时间", // ------------------------------------------------------------------------------------------------------------- // pages diff --git a/src/utils/formatterUtils.ts b/src/utils/formatterUtils.ts index ad475d2..4e1f73b 100644 --- a/src/utils/formatterUtils.ts +++ b/src/utils/formatterUtils.ts @@ -1,5 +1,5 @@ import {FormatRequest, Formatter} from "@/types/formatter"; -import {ExportFormat} from "@/constants/enums"; +import {ExportFormat, ValueType} from "@/constants/enums"; import {formatters} from "@/core/formatters"; import {langs} from '@uiw/codemirror-extensions-langs'; import {DataFieldList} from "@/types/generator"; @@ -103,6 +103,8 @@ export function toJsonListStringWithoutQuotes(fields: DataFieldList, sortedField if (Array.isArray(value)) { // 处理数组格式 const elements = value.map(element => convert(element, indent + 2)); return `[\n${nextIndentSpace}${elements.join(`,\n${nextIndentSpace}`)}\n${indentSpace}]`; + } else if (value instanceof Date) { + return `new Date("${toISOString(value)}")` } else if (typeof value === 'object' && value !== null) { // 处理对象格式 const entries = Object.entries(value).map(([key, val]) => { const formattedValue = convert(val, indent + 2); @@ -137,3 +139,19 @@ export function toJsonListStringWithoutQuotes(fields: DataFieldList, sortedField output += "]"; // 结束数组 return output; } + + +function toISOString(date) { + // 获取日期部分 + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份是从0开始的 + const day = String(date.getDate()).padStart(2, '0'); + + // 获取时间部分 + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + const seconds = String(date.getSeconds()).padStart(2, '0'); + + // 拼接成 YYYY-MM-DDTHH:mm:ss 格式 + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`; +} \ No newline at end of file diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index b6f247c..1c50988 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -13,4 +13,19 @@ export const calculateByteSize = (str: string) => { // check variable has value export function hasValue(variable: any): boolean { return variable !== null && variable !== undefined; +} + +export function toDateString(date: Date): string { + const day = date.getDate().toString().padStart(2, '0'); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const year = date.getFullYear(); + return `${day}-${month}-${year}`; +} + +export function toDateTimeString(date: Date): string { + const formattedDate = toDateString(date); // Reuse formatDate function + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const seconds = date.getSeconds().toString().padStart(2, '0'); + return `${formattedDate} ${hours}:${minutes}:${seconds}`; } \ No newline at end of file