Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions browser/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This changelog covers all three packages, as they are (for now) updated as a whole

## Unreleased

### Atomic Browser

- [#841](https://github.com/atomicdata-dev/atomic-server/issues/841) Add better inputs for `Timestamp` and `Date` datatypes.

## v0.37.0

### Atomic Browser
Expand Down
61 changes: 61 additions & 0 deletions browser/data-browser/src/components/forms/InputDate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { InputProps } from './ResourceField';
import { useValidation } from './formValidation/useValidation';
import { styled } from 'styled-components';
import { ErrorChipInput } from './ErrorChip';
import { useString, validateDatatype } from '@tomic/react';
import { InputStyled, InputWrapper } from './InputStyles';
import { ChangeEvent } from 'react';

export function InputDate({
resource,
property,
commit,
required,
...props
}: InputProps): React.JSX.Element {
const [err, setErr, onBlur] = useValidation();
const [value, setValue] = useString(resource, property.subject, {
commit,
validate: false,
});

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const dateStr = event.target.value;

if (required && dateStr) {
setErr('Required');
setValue(undefined);
} else {
try {
validateDatatype(dateStr, property.datatype);
setValue(dateStr);
setErr(undefined);
} catch (e) {
setErr(e);
}
}
};

return (
<Wrapper>
<StyledInputWrapper>
<InputStyled
type='date'
value={value}
onChange={handleChange}
onBlur={onBlur}
{...props}
/>
</StyledInputWrapper>
{err && <ErrorChipInput>{err}</ErrorChipInput>}
</Wrapper>
);
}

const Wrapper = styled.div`
position: relative;
`;

const StyledInputWrapper = styled(InputWrapper)`
width: min-content;
`;
9 changes: 7 additions & 2 deletions browser/data-browser/src/components/forms/InputSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import InputMarkdown from './InputMarkdown';
import InputNumber from './InputNumber';
import InputBoolean from './InputBoolean';
import InputSlug from './InputSlug';
import { InputTimestamp } from './InputTimestamp';
import { InputDate } from './InputDate';

/** Renders a fitting HTML input depending on the Datatype */
export default function InputSwitcher(props: InputProps): JSX.Element {
Expand Down Expand Up @@ -44,9 +46,12 @@ export default function InputSwitcher(props: InputProps): JSX.Element {
return <InputBoolean {...props} />;
}

// TODO: DateTime selector
case Datatype.TIMESTAMP: {
return <InputNumber {...props} />;
return <InputTimestamp {...props} />;
}

case Datatype.DATE: {
return <InputDate {...props} />;
}

default: {
Expand Down
59 changes: 59 additions & 0 deletions browser/data-browser/src/components/forms/InputTimestamp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useNumber } from '@tomic/react';
import { InputProps } from './ResourceField';
import { useValidation } from './formValidation/useValidation';
import { styled } from 'styled-components';
import { ErrorChipInput } from './ErrorChip';
import { InputStyled, InputWrapper } from './InputStyles';
import { useDateTimeInput } from './hooks/useDateTimeInput';

export function InputTimestamp({
resource,
property,
commit,
required,
...props
}: InputProps): React.JSX.Element {
const [err, setErr, onBlur] = useValidation();
const [value, setValue] = useNumber(resource, property.subject, {
commit,
validate: false,
});

const [localDate, handleChange] = useDateTimeInput(
value,
(time: number | undefined) => {
if (required && time === undefined) {
setErr('Required');
setValue(undefined);
} else {
setErr(undefined);
setValue(time);
}
},
);

return (
<Wrapper>
<StyledInputWrapper $invalid={!!err}>
<InputStyled
type='datetime-local'
value={localDate}
required={required}
onChange={handleChange}
onBlur={onBlur}
{...props}
/>
</StyledInputWrapper>
{err && <ErrorChipInput>{err}</ErrorChipInput>}
</Wrapper>
);
}

const Wrapper = styled.div`
flex: 1;
position: relative;
`;

const StyledInputWrapper = styled(InputWrapper)`
width: min-content;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { isNumber } from '@tomic/react';
import { useCallback } from 'react';

const pad = (value: number): string => `${value}`.padStart(2, '0');

const timestampToDateTimeLocal = (timestamp: number): string => {
const date = new Date(timestamp);

const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours();
const minutes = date.getMinutes();

return `${year}-${pad(month)}-${pad(day)}T${pad(hours)}:${pad(minutes)}`;
};

const dateTimeLocalToTimestamp = (dateTimeLocal: string): number => {
const date = new Date(dateTimeLocal);

return date.getTime();
};

export const useDateTimeInput = (
value: number | undefined,
onChange: (value: number | undefined) => void,
): [
localDate: string | undefined,
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
] => {
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value) {
onChange(dateTimeLocalToTimestamp(e.target.value));
} else {
onChange(undefined);
}
},
[onChange],
);

let localDate: string | undefined = undefined;

if (isNumber(value)) {
localDate = timestampToDateTimeLocal(value);
}

return [localDate, handleChange];
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,17 @@ import {
useResource,
useString,
} from '@tomic/react';
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { formatDate } from '../../../helpers/dates/formatDate';
import { InputBase } from './InputBase';
import { CellContainer, DisplayCellProps, EditCellProps } from './Type';

const pad = (value: number): string => `${value}`.padStart(2, '0');

const buildDateTimeLocalString = (date: Date): string => {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hours = date.getHours();
const minutes = date.getMinutes();

return `${year}-${pad(month)}-${pad(day)}T${pad(hours)}:${pad(minutes)}`;
};
import { useDateTimeInput } from '../../../components/forms/hooks/useDateTimeInput';

function DateTimeCellEdit({
value,
onChange,
}: EditCellProps<JSONValue>): JSX.Element {
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const date = new Date(e.target.value);
onChange(date.getTime());
},
[onChange],
);

let localDate: string | undefined = undefined;

if (isNumber(value)) {
localDate = buildDateTimeLocalString(new Date(value));
}
const [localDate, handleChange] = useDateTimeInput(value as number, onChange);

return (
<InputBase
Expand Down
Loading