diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 7252995..e0cacc1 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -1,55 +1,61 @@
# Table of contents
-* [Home](README.md)
+- [Home](README.md)
## Components
-* [Datagrid](components/datagrid.md)
-* [Fields](components/fields/README.md)
- * [BooleanField](components/fields/booleanfield.md)
- * [ChipField](components/fields/chipfield.md)
- * [CountryField](components/fields/countryfield.md)
- * [CurrencyField](components/fields/currencyfield.md)
- * [DateField](components/fields/datefield.md)
- * [FileViewerField](components/fields/fileviewerfield.md)
- * [MaskedNumberField](components/fields/maskednumberfield.md)
- * [PhoneField](components/fields/phonefield.md)
- * [ReferenceArrayField](components/fields/referencearrayfield.md)
- * [ReferenceField](components/fields/referencefield.md)
- * [RichTextField](components/fields/richtextfield.md)
- * [SelectField](components/fields/selectfield.md)
- * [TextField](components/fields/textfield.md)
- * [TimeField](components/fields/timefield.md)
- * [TimelineArrayField](components/fields/timelinearrayfield.md)
-* [Inputs](components/inputs/README.md)
- * [CountryInput](components/inputs/countryinput.md)
- * [CurrencyInput](components/inputs/currencyinput.md)
- * [DateInput](components/inputs/dateinput.md)
- * [MapsInput](components/inputs/mapsinput.md)
- * [MaskedNumberInput](components/inputs/maskednumberinput.md)
- * [MaskedTextInput](components/inputs/maskedtextinput.md)
- * [PhoneInput](components/inputs/phoneinput.md)
- * [PlacesTimelineInput](components/inputs/placestimelineinput.md)
- * [TimeInput](components/inputs/timeinput.md)
-* [ResponsiveDatagrid](components/responsivedatagrid.md)
+- [Datagrid](components/datagrid.md)
+- [Fields](components/fields/README.md)
+ - [BooleanField](components/fields/booleanfield.md)
+ - [ChipField](components/fields/chipfield.md)
+ - [CountryField](components/fields/countryfield.md)
+ - [CurrencyField](components/fields/currencyfield.md)
+ - [DateField](components/fields/datefield.md)
+ - [FileViewerField](components/fields/fileviewerfield.md)
+ - [MaskedNumberField](components/fields/maskednumberfield.md)
+ - [PhoneField](components/fields/phonefield.md)
+ - [ReferenceArrayField](components/fields/referencearrayfield.md)
+ - [ReferenceField](components/fields/referencefield.md)
+ - [RichTextField](components/fields/richtextfield.md)
+ - [SelectField](components/fields/selectfield.md)
+ - [TextField](components/fields/textfield.md)
+ - [TimeField](components/fields/timefield.md)
+ - [TimelineArrayField](components/fields/timelinearrayfield.md)
+ - [TimezoneField](components/fields/timezonefield.md)
+ - [TzTimeField](components/fields/tztimefield.md)
+ - [TzDateField](components/fields/tzdatefield.md)
+- [Inputs](components/inputs/README.md)
+ - [CountryInput](components/inputs/countryinput.md)
+ - [CurrencyInput](components/inputs/currencyinput.md)
+ - [DateInput](components/inputs/dateinput.md)
+ - [MapsInput](components/inputs/mapsinput.md)
+ - [MaskedNumberInput](components/inputs/maskednumberinput.md)
+ - [MaskedTextInput](components/inputs/maskedtextinput.md)
+ - [PhoneInput](components/inputs/phoneinput.md)
+ - [PlacesTimelineInput](components/inputs/placestimelineinput.md)
+ - [TimeInput](components/inputs/timeinput.md)
+ - [TimezoneInput](components/fields/timezoneinput.md)
+ - [TzTimeInput](components/fields/tztimeinput.md)
+ - [TzDateInput](components/fields/tzdateinput.md)
+- [ResponsiveDatagrid](components/responsivedatagrid.md)
## Hooks
-* [useScreenSize](hooks/usescreensize.md)
-* [useCurrencies](hooks/usecurrencies.md)
-* [useCountries](hooks/usecountries.md)
+- [useScreenSize](hooks/usescreensize.md)
+- [useCurrencies](hooks/usecurrencies.md)
+- [useCountries](hooks/usecountries.md)
## Services
-* [HttpRequest](services/httprequest.md)
-* [LocalSession](services/localsession.md)
-* [raDataRestProvider](services/radatarestprovider.md)
+- [HttpRequest](services/httprequest.md)
+- [LocalSession](services/localsession.md)
+- [raDataRestProvider](services/radatarestprovider.md)
## Providers
-* [DataProvider](providers/dataprovider.md)
+- [DataProvider](providers/dataprovider.md)
## Validations
-* [minYear](validations/minyear.md)
-* [maxYear](validations/maxyear.md)
+- [minYear](validations/minyear.md)
+- [maxYear](validations/maxyear.md)
diff --git a/docs/components/fields/timezonefield.md b/docs/components/fields/timezonefield.md
new file mode 100644
index 0000000..cc0adea
--- /dev/null
+++ b/docs/components/fields/timezonefield.md
@@ -0,0 +1,19 @@
+# TimezoneField
+
+TimezoneField component that map IANA timezone to text with Labeled component.
+
+### Usage
+
+```tsx
+import { TimezoneField } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| label | false | string | | the label to display. |
+| source | true | string | | the field source to retrieve its value. |
diff --git a/docs/components/fields/tzdatefield.md b/docs/components/fields/tzdatefield.md
new file mode 100644
index 0000000..9970249
--- /dev/null
+++ b/docs/components/fields/tzdatefield.md
@@ -0,0 +1,19 @@
+# TzDateField
+
+TzDateField component uses ra DateField with Labeled component and timezone.
+
+### Usage
+
+```tsx
+import { TzDateField } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| source | true | string | | the field source to retrieve its value. |
+| timezoneSource | true | string | | the timezone field source to retrieve its value. |
diff --git a/docs/components/fields/tztimefield.md b/docs/components/fields/tztimefield.md
new file mode 100644
index 0000000..13e3e4e
--- /dev/null
+++ b/docs/components/fields/tztimefield.md
@@ -0,0 +1,19 @@
+# TzTimeField
+
+TzTimeField component uses ra DateField (time-only) with Labeled component and timezone.
+
+### Usage
+
+```tsx
+import { TzTimeField } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| source | true | string | | the field source to retrieve its value. |
+| timezoneSource | true | string | | the timezone field source to retrieve its value. |
diff --git a/docs/components/inputs/timezoneinput.md b/docs/components/inputs/timezoneinput.md
new file mode 100644
index 0000000..ba57f25
--- /dev/null
+++ b/docs/components/inputs/timezoneinput.md
@@ -0,0 +1,19 @@
+# TimezoneInput
+
+TimezoneInput component that uses mui Autocomplete with IANA list.
+
+### Usage
+
+```tsx
+import { TimezoneInput } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| label | false | string | | the label to display. |
+| source | true | string | | the field source to retrieve its value. |
diff --git a/docs/components/inputs/tzdateinput.md b/docs/components/inputs/tzdateinput.md
new file mode 100644
index 0000000..48d8661
--- /dev/null
+++ b/docs/components/inputs/tzdateinput.md
@@ -0,0 +1,19 @@
+# TzDateInput
+
+TzDateInput component uses mui Date picker with Dayjs timezone.
+
+### Usage
+
+```tsx
+import { TzDateInput } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| source | true | string | | the field source to retrieve its value. |
+| timezoneSource | true | string | | the timezone field source to retrieve its value. |
diff --git a/docs/components/inputs/tztimeinput.md b/docs/components/inputs/tztimeinput.md
new file mode 100644
index 0000000..ac89c48
--- /dev/null
+++ b/docs/components/inputs/tztimeinput.md
@@ -0,0 +1,19 @@
+# TzTimeInput
+
+TzTimeInput component uses mui Time Picker with Dayjs timezone.
+
+### Usage
+
+```tsx
+import { TzTimeInput } from '@ra-libs/react';
+
+;
+```
+
+### Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | ------- | ------- | ---------------------------------------------------- |
+| useLabel | false | boolean | false | whether to use react-admin Labeled component or not. |
+| source | true | string | | the field source to retrieve its value. |
+| timezoneSource | true | string | | the timezone field source to retrieve its value. |
diff --git a/package-lock.json b/package-lock.json
index bf79bc4..27fafcf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,7 +29,8 @@
"react-hook-form": "^7.45.0",
"react-imask": "^7.0.1",
"react-number-format": "^5.2.2",
- "scheduler": "^0.23.0"
+ "scheduler": "^0.23.0",
+ "timezones.json": "^1.7.1"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
@@ -12968,6 +12969,11 @@
"xtend": "~4.0.1"
}
},
+ "node_modules/timezones.json": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/timezones.json/-/timezones.json-1.7.1.tgz",
+ "integrity": "sha512-4dB58ulcrRWfiGufzlofLG45RIoalCTZiFUc7tnj0g8za0CpNTyIOVlspg1JD7OFyDeW5up3ntlkukizwB0IJA=="
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -22344,6 +22350,11 @@
"xtend": "~4.0.1"
}
},
+ "timezones.json": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/timezones.json/-/timezones.json-1.7.1.tgz",
+ "integrity": "sha512-4dB58ulcrRWfiGufzlofLG45RIoalCTZiFUc7tnj0g8za0CpNTyIOVlspg1JD7OFyDeW5up3ntlkukizwB0IJA=="
+ },
"tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
diff --git a/package.json b/package.json
index 7333862..336ada5 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,8 @@
"react-hook-form": "^7.45.0",
"react-imask": "^7.0.1",
"react-number-format": "^5.2.2",
- "scheduler": "^0.23.0"
+ "scheduler": "^0.23.0",
+ "timezones.json": "^1.7.1"
},
"peerDependencies": {
"react": ">=16"
diff --git a/src/components/fields/TimezoneField/index.tsx b/src/components/fields/TimezoneField/index.tsx
new file mode 100644
index 0000000..724bf3b
--- /dev/null
+++ b/src/components/fields/TimezoneField/index.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import { FunctionField, Labeled, TextFieldProps, useRecordContext, useResourceContext, useTranslate } from 'react-admin'
+import timezones from 'timezones.json'
+
+import { LabeledFieldProps } from '../../../config'
+
+interface TimezoneFieldProps extends LabeledFieldProps {
+ source: string
+}
+
+export function TimezoneField(props: TimezoneFieldProps) {
+ const { useLabel, source } = props
+
+ const record = useRecordContext()
+
+ const timezone = timezones.find((timezone) => timezone.utc[0] === record?.[source])
+
+ const resource = useResourceContext()
+ const translate = useTranslate()
+
+ const label = props.label ? props.label : translate(`resources.${resource}.fields.${source}`)
+
+ const field = timezone?.text} />
+ return useLabel ? {field} : <>{field}>
+}
diff --git a/src/components/fields/TzDateField/index.tsx b/src/components/fields/TzDateField/index.tsx
new file mode 100644
index 0000000..80eb8d1
--- /dev/null
+++ b/src/components/fields/TzDateField/index.tsx
@@ -0,0 +1,39 @@
+import dayjs from 'dayjs'
+import timezone from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
+import React from 'react'
+import { DateField as RaDateField, DateFieldProps, Labeled, useRecordContext } from 'react-admin'
+
+import { LabeledFieldProps } from '../../../config'
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+interface TzDateFieldProps extends LabeledFieldProps {
+ timezoneSource: string
+ source: string
+}
+
+export function TzDateField(props: TzDateFieldProps) {
+ const { useLabel, timezoneSource, ...rest } = props
+
+ const record = useRecordContext()
+ const timezone = record?.[timezoneSource]
+ const date = record?.[rest.source]
+ const utcDate = dayjs(date)
+
+ const field = (
+ {
+ return utcDate.toDate()
+ }}
+ options={{
+ timeZone: timezone,
+ }}
+ />
+ )
+ return useLabel ? {field} : <>{field}>
+}
diff --git a/src/components/fields/TzTimeField/index.tsx b/src/components/fields/TzTimeField/index.tsx
new file mode 100644
index 0000000..1550878
--- /dev/null
+++ b/src/components/fields/TzTimeField/index.tsx
@@ -0,0 +1,38 @@
+import dayjs from 'dayjs'
+import timezone from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
+import React from 'react'
+import { DateField as RaDateField, DateFieldProps, Labeled, useRecordContext } from 'react-admin'
+
+import { LabeledFieldProps } from '../../../config'
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+interface TzTimeFieldProps extends LabeledFieldProps {
+ timezoneSource: string
+ source: string
+}
+
+export function TzTimeField(props: TzTimeFieldProps) {
+ const { useLabel, timezoneSource, ...rest } = props
+ const record = useRecordContext()
+ const timezone = record?.[timezoneSource]
+ const date = record?.[rest.source]
+ const utcDate = dayjs(date)
+
+ const field = (
+ utcDate.toDate()}
+ options={{
+ hour: '2-digit',
+ minute: '2-digit',
+ timeZone: timezone,
+ }}
+ />
+ )
+ return useLabel ? {field} : <>{field}>
+}
diff --git a/src/components/fields/index.ts b/src/components/fields/index.ts
index efb59c3..e753287 100644
--- a/src/components/fields/index.ts
+++ b/src/components/fields/index.ts
@@ -13,3 +13,6 @@ export * from './CountryField'
export * from './TimelineArrayField'
export * from './TimeField'
export * from './PhoneField'
+export * from './TzDateField'
+export * from './TzTimeField'
+export * from './TimezoneField'
diff --git a/src/components/inputs/MapsInput/index.tsx b/src/components/inputs/MapsInput/index.tsx
index 85cc772..418d5b4 100644
--- a/src/components/inputs/MapsInput/index.tsx
+++ b/src/components/inputs/MapsInput/index.tsx
@@ -1,17 +1,17 @@
-import React, { useEffect } from 'react'
-import Box from '@mui/material/Box'
-import TextField from '@mui/material/TextField'
-import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'
import LocationOnIcon from '@mui/icons-material/LocationOn'
+import { debounce } from '@mui/material'
+import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'
+import Box from '@mui/material/Box'
import Grid from '@mui/material/Grid'
+import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import parse from 'autosuggest-highlight/parse'
-import { throttle } from 'lodash'
-
+import React, { useEffect } from 'react'
import { TextInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
-import { HttpRequest } from '../../../services'
import { useFormContext } from 'react-hook-form'
+import { HttpRequest } from '../../../services'
+
interface MapsInputProps extends TextInputProps {
source: string
useMainText?: boolean
@@ -73,7 +73,7 @@ export function MapsInput(props: MapsInputProps) {
const fetch = React.useMemo(
() =>
- throttle((input: string, callback: (results?: readonly PlaceType[]) => void) => {
+ debounce((input: string, callback: (results?: readonly PlaceType[]) => void) => {
getPlacePredictions(
API_URL,
API_SEARCH_FIELD,
@@ -81,7 +81,7 @@ export function MapsInput(props: MapsInputProps) {
{ search: input, mapType: props.mapType },
callback,
)
- }, 200),
+ }, 400),
[],
)
diff --git a/src/components/inputs/PlacesTimelineInput/index.tsx b/src/components/inputs/PlacesTimelineInput/index.tsx
index 800a9d1..b818f6a 100644
--- a/src/components/inputs/PlacesTimelineInput/index.tsx
+++ b/src/components/inputs/PlacesTimelineInput/index.tsx
@@ -1,15 +1,5 @@
-import React, { useEffect } from 'react'
-import Box from '@mui/material/Box'
-import TextField from '@mui/material/TextField'
-import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'
+import DeleteIcon from '@mui/icons-material/Delete'
import LocationOnIcon from '@mui/icons-material/LocationOn'
-import Grid from '@mui/material/Grid'
-import Typography from '@mui/material/Typography'
-import parse from 'autosuggest-highlight/parse'
-import { throttle } from 'lodash'
-import { useFormContext } from 'react-hook-form'
-
-import { TextInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
import {
Timeline,
TimelineConnector,
@@ -19,9 +9,17 @@ import {
TimelineOppositeContent,
TimelineSeparator,
} from '@mui/lab'
-import { IconButton } from '@mui/material'
+import { debounce, IconButton } from '@mui/material'
+import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete'
+import Box from '@mui/material/Box'
+import Grid from '@mui/material/Grid'
+import TextField from '@mui/material/TextField'
+import Typography from '@mui/material/Typography'
+import parse from 'autosuggest-highlight/parse'
+import React, { useEffect } from 'react'
+import { TextInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
+import { useFormContext } from 'react-hook-form'
-import DeleteIcon from '@mui/icons-material/Delete'
import { HttpRequest } from '../../../services'
interface PlacesTimelineInputProps extends TextInputProps {
@@ -84,7 +82,7 @@ export function PlacesTimelineInput(props: PlacesTimelineInputProps) {
const fetch = React.useMemo(
() =>
- throttle((input: string, callback: (results?: readonly PlaceType[]) => void) => {
+ debounce((input: string, callback: (results?: readonly PlaceType[]) => void) => {
getPlacePredictions(
API_URL,
API_SEARCH_FIELD,
@@ -92,7 +90,7 @@ export function PlacesTimelineInput(props: PlacesTimelineInputProps) {
{ search: input, mapType: props.mapType },
callback,
)
- }, 200),
+ }, 400),
[],
)
diff --git a/src/components/inputs/TimezoneInput/index.tsx b/src/components/inputs/TimezoneInput/index.tsx
new file mode 100644
index 0000000..f89a62e
--- /dev/null
+++ b/src/components/inputs/TimezoneInput/index.tsx
@@ -0,0 +1,82 @@
+import { Autocomplete, TextField } from '@mui/material'
+import React, { useEffect, useState } from 'react'
+import { DateInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
+import { useFormContext } from 'react-hook-form'
+import timezones from 'timezones.json'
+
+interface OptionValue {
+ label: string
+ value: string
+}
+
+export function TimezoneInput(props: DateInputProps) {
+ const autoCompletesTimezones: OptionValue[] = timezones.map((timezone) => {
+ return { label: timezone.text, value: timezone.utc[0] }
+ })
+
+ const translate = useTranslate()
+ const { setValue: setFormValue } = useFormContext()
+
+ const { margin = 'dense', defaultValue = 'America/Sao_Paulo' } = props
+
+ const {
+ field,
+ fieldState: { isTouched, invalid, error },
+ formState: { isSubmitted },
+ isRequired,
+ id,
+ } = useInput({ source: props.source })
+
+ const hasError = (isTouched || isSubmitted) && invalid
+
+ const resource = useResourceContext()
+ const label = props.label ? props.label : translate(`resources.${resource}.fields.${field.name}`)
+
+ const timezoneDefaultValue = autoCompletesTimezones.find((timezone) => timezone.value === defaultValue)
+
+ const [optionValue, setOptionValue] = useState(timezoneDefaultValue)
+
+ useEffect(() => {
+ if (!field.value) {
+ setFormValue(props.source, timezoneDefaultValue?.value, {
+ shouldDirty: true,
+ })
+ }
+ }, [])
+
+ useEffect(() => {
+ if (field.value) {
+ const timezone = autoCompletesTimezones.find((timezone) => timezone.value === field.value)
+ setOptionValue(timezone)
+ }
+ }, [field.value])
+
+ const handleValueChange = (_: React.SyntheticEvent, newOptionValue: OptionValue | null) => {
+ setFormValue(props.source, newOptionValue?.value, {
+ shouldDirty: true,
+ })
+ }
+
+ return (
+ (
+
+ )}
+ />
+ )
+}
diff --git a/src/components/inputs/TzDateInput/index.tsx b/src/components/inputs/TzDateInput/index.tsx
new file mode 100644
index 0000000..18e8325
--- /dev/null
+++ b/src/components/inputs/TzDateInput/index.tsx
@@ -0,0 +1,76 @@
+import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
+import { DatePicker } from '@mui/x-date-pickers/DatePicker'
+import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
+import dayjs, { Dayjs } from 'dayjs'
+import timezone from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
+import React, { useEffect } from 'react'
+import { DateInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
+import { useFormContext } from 'react-hook-form'
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+interface TzDateInputProps extends DateInputProps {
+ timezoneSource: string
+ adapterLocale?: string
+}
+
+export function TzDateInput(props: TzDateInputProps) {
+ const translate = useTranslate()
+
+ const { setValue: setFormValue } = useFormContext()
+
+ const { margin = 'dense', adapterLocale = 'pt-br' } = props
+
+ const {
+ field,
+ fieldState: { isTouched, invalid, error },
+ formState: { isSubmitted },
+ isRequired,
+ id,
+ } = useInput(props)
+
+ const hasError = (isTouched || isSubmitted) && invalid
+
+ const resource = useResourceContext()
+ const label = props.label ? props.label : translate(`resources.${resource}.fields.${field.name}`)
+
+ const timezoneSourceInput = useInput({
+ source: props.timezoneSource,
+ })
+
+ const [value, setValue] = React.useState()
+
+ const handleValueChange = (newValue: Dayjs | null) => {
+ setFormValue(field.name, newValue?.toISOString(), { shouldDirty: true })
+ }
+
+ useEffect(() => {
+ if (field.value) {
+ const date = dayjs.utc(field.value)
+ setValue(date.tz(timezoneSourceInput.field.value))
+ }
+ }, [field.value])
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/inputs/TzTimeInput/index.tsx b/src/components/inputs/TzTimeInput/index.tsx
new file mode 100644
index 0000000..36d381c
--- /dev/null
+++ b/src/components/inputs/TzTimeInput/index.tsx
@@ -0,0 +1,91 @@
+import 'dayjs/locale/pt-br'
+
+import { LocalizationProvider } from '@mui/x-date-pickers'
+import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
+import { TimePicker } from '@mui/x-date-pickers/TimePicker'
+import dayjs, { Dayjs } from 'dayjs'
+import timezone from 'dayjs/plugin/timezone'
+import utc from 'dayjs/plugin/utc'
+import React, { useEffect } from 'react'
+import { DateInputProps, useInput, useResourceContext, useTranslate } from 'react-admin'
+import { useFormContext } from 'react-hook-form'
+
+interface TzTimeInputProps extends DateInputProps {
+ timezoneSource: string
+ adapterLocale?: string
+ dateSource?: string
+}
+
+dayjs.extend(utc)
+dayjs.extend(timezone)
+
+export function TzTimeInput(props: TzTimeInputProps) {
+ const translate = useTranslate()
+ const { setValue: setFormValue } = useFormContext()
+
+ const { margin = 'dense', adapterLocale = 'pt-br', dateSource } = props
+
+ const {
+ field,
+ fieldState: { isTouched, invalid, error },
+ formState: { isSubmitted },
+ isRequired,
+ id,
+ } = useInput({ source: props.source })
+
+ const timezoneSourceInput = useInput({
+ source: props.timezoneSource,
+ })
+
+ const [value, setValue] = React.useState(null)
+
+ const hasError = (isTouched || isSubmitted) && invalid
+
+ const resource = useResourceContext()
+
+ const label = props.label ? props.label : translate(`resources.${resource}.fields.${field.name}`)
+
+ const dateSourceInput = useInput({
+ source: dateSource || '',
+ })
+
+ const handleValueChange = (newValue: Dayjs | null) => {
+ if (dateSourceInput.id !== id) {
+ const dateSourceValue = dateSourceInput.field.value as Date
+ newValue?.set('year', dateSourceValue.getUTCFullYear())
+ newValue?.set('month', dateSourceValue.getUTCMonth())
+ newValue?.set('date', dateSourceValue.getUTCDate())
+ }
+
+ setFormValue(field.name, newValue?.toISOString(), {
+ shouldDirty: true,
+ })
+ }
+
+ useEffect(() => {
+ if (field.value) {
+ const date = dayjs.utc(field.value)
+ setValue(dayjs.tz(date, timezoneSourceInput.field.value))
+ }
+ }, [field.value])
+
+ return (
+
+
+
+ )
+}
diff --git a/src/components/inputs/index.ts b/src/components/inputs/index.ts
index 92bfc72..121335d 100644
--- a/src/components/inputs/index.ts
+++ b/src/components/inputs/index.ts
@@ -7,3 +7,6 @@ export * from './PlacesTimelineInput'
export * from './TimeInput'
export * from './MapsInput'
export * from './PhoneInput'
+export * from './TzDateInput'
+export * from './TzTimeInput'
+export * from './TimezoneInput'