Skip to content

Commit

Permalink
chore(web): date time field input (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkumbobeaty authored Oct 27, 2023
1 parent fe3e6af commit 466362e
Show file tree
Hide file tree
Showing 14 changed files with 365 additions and 28 deletions.
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
"lodash-es": "4.17.21",
"lru-cache": "8.0.4",
"mini-svg-data-uri": "1.4.4",
"moment-timezone": "0.5.43",
"parse-domain": "7.0.1",
"quickjs-emscripten": "0.23.0",
"quickjs-emscripten-sync": "1.5.2",
Expand Down
3 changes: 3 additions & 0 deletions web/src/beta/components/Icon/Icons/Clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions web/src/beta/components/Icon/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import ZoomToLayer from "./Icons/zoomToLayer.svg";
import LayerStyleIcon from "./Icons/layerStyle.svg";
import AddLayerStyleButtonIcon from "./Icons/addLayerStyleButton.svg";
import LayerInspector from "./Icons/layerInspector.svg";
import Clock from "./Icons/Clock.svg";

// MSIC
import CheckCircle from "./Icons/checkCircle.svg";
Expand Down Expand Up @@ -123,6 +124,7 @@ export default {
text: InfoText,
html: InfoHTML,
video: InfoVideo,
clock: Clock,
location: InfoLocation,
photooverlay: PrimPhotoOverlay,
arrowUpDown: ArrowUpDown,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Camera } from "@reearth/beta/utils/value";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import PanelCommon from "../PanelCommon";
import PanelCommon from "../../common/PanelCommon";
import type { RowType } from "../types";

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Camera } from "@reearth/beta/utils/value";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import PanelCommon from "../PanelCommon";
import PanelCommon from "../../common/PanelCommon";

import useHooks from "./hooks";

Expand Down
89 changes: 89 additions & 0 deletions web/src/beta/components/fields/DateTimeField/EditPanel/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import moment from "moment-timezone";
import { useCallback, useEffect, useMemo, useState } from "react";

import { getUniqueTimezones } from "@reearth/beta/utils/moment-timezone";

type Props = {
value?: string;
onChange?: (value?: string | undefined) => void;
setDateTime?: (value?: string | undefined) => void;
};

type TimezoneInfo = {
timezone: string;
offset: string;
};

export default ({ value, onChange, setDateTime }: Props) => {
const [date, setDate] = useState("");
const [time, setTime] = useState("");
const [selectedTimezone, setSelectedTimezone] = useState<TimezoneInfo>({
offset: "+0:00",
timezone: "Africa/Abidjan",
});

const handleTimeChange = useCallback((newValue: string | undefined) => {
if (newValue === undefined) return;
setTime(newValue);
}, []);

const handleDateChange = useCallback((newValue: string | undefined) => {
if (newValue === undefined) return;
setDate(newValue);
}, []);

const offsetFromUTC: TimezoneInfo[] = useMemo(() => {
return getUniqueTimezones(moment.tz.names());
}, []);

const handleApplyChange = useCallback(() => {
const selectedTimezoneInfo = offsetFromUTC.find(
info => info.timezone === selectedTimezone.timezone,
);
if (selectedTimezoneInfo) {
const formattedDateTime = `${date}T${time}:00${selectedTimezoneInfo.offset}`;
setDateTime?.(formattedDateTime);
onChange?.(formattedDateTime);
}
}, [offsetFromUTC, selectedTimezone, date, time, setDateTime, onChange]);

const handleTimezoneSelect = useCallback(
(newValue: string) => {
const updatedTimezone = offsetFromUTC.find(info => info.timezone === newValue);
setSelectedTimezone(updatedTimezone || selectedTimezone);
},
[offsetFromUTC, selectedTimezone],
);

useEffect(() => {
if (value) {
const [parsedDate, timeWithOffset] = value.split("T");
const [parsedTime, timezoneOffset] = timeWithOffset.split(/[-+]/);

setDate(parsedDate);
setTime(parsedTime);

const updatedTimezone = offsetFromUTC.find(
info =>
info.offset ===
(timeWithOffset.includes("-") ? `-${timezoneOffset}` : `+${timezoneOffset}`),
);
updatedTimezone && setSelectedTimezone(updatedTimezone);
} else {
setDate("");
setTime("");
setSelectedTimezone({ offset: "+0:00", timezone: "Africa/Abidjan" });
}
}, [value, offsetFromUTC]);

return {
date,
time,
selectedTimezone,
offsetFromUTC,
onTimeChange: handleTimeChange,
onTimezoneSelect: handleTimezoneSelect,
onDateChange: handleDateChange,
onDateTimeApply: handleApplyChange,
};
};
120 changes: 120 additions & 0 deletions web/src/beta/components/fields/DateTimeField/EditPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useMemo } from "react";

import Button from "@reearth/beta/components/Button";
import PanelCommon from "@reearth/beta/components/fields/common/PanelCommon";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import TextInput from "../../common/TextInput";
import SelectField from "../../SelectField";

import useHooks from "./hooks";

type Props = {
onChange?: (value?: string | undefined) => void;
onClose: () => void;
value?: string;
setDateTime?: (value?: string | undefined) => void;
};

const EditPanel: React.FC<Props> = ({ onChange, onClose, value, setDateTime }) => {
const t = useT();

const {
date,
time,
selectedTimezone,
offsetFromUTC,
onDateChange,
onTimeChange,
onTimezoneSelect,
onDateTimeApply,
} = useHooks({ value, onChange, setDateTime });

const isButtonDisabled = useMemo(() => {
return date.trim() === "" || time.trim() === "";
}, [date, time]);

return (
<PanelCommon title={t("Set Time")} onClose={onClose}>
<FieldGroup>
<TextWrapper>
<Label>{t("Date")}</Label>
<Input type="date" value={date} onChange={onDateChange} />
</TextWrapper>
<TextWrapper>
<Label>{t("Time")}</Label>

<Input type="time" value={time} onChange={onTimeChange} />
</TextWrapper>
<SelectWrapper>
<Label>{t("Time Zone")}</Label>
<CustomSelect
value={selectedTimezone.timezone}
options={offsetFromUTC.map(timezone => ({
key: timezone.timezone,
label: timezone?.offset,
}))}
onChange={onTimezoneSelect}
/>
</SelectWrapper>
</FieldGroup>
<Divider />
<ButtonWrapper>
<StyledButton text={t("Cancel")} size="small" onClick={onClose} />
<StyledButton
text={t("Apply")}
size="small"
buttonType="primary"
onClick={() => {
onDateTimeApply(), onClose();
}}
disabled={isButtonDisabled}
/>
</ButtonWrapper>
</PanelCommon>
);
};

const TextWrapper = styled.div`
margin-left: 8px;
width: 88%;
`;

const Input = styled(TextInput)`
width: 100%;
`;

const FieldGroup = styled.div`
padding-bottom: 8px;
`;

const Label = styled.div`
font-size: 12px;
padding: 10px 0;
`;

const Divider = styled.div`
border-top: 1px solid ${({ theme }) => theme.outline.weak};
`;

const ButtonWrapper = styled.div`
display: flex;
gap: 8px;
padding: 8px;
`;

const StyledButton = styled(Button)`
flex: 1;
`;

const SelectWrapper = styled.div`
margin-left: 8px;
width: 95%;
`;
const CustomSelect = styled(SelectField)`
height: 120px;
overflow-y: auto;
width: 100%;
`;
export default EditPanel;
117 changes: 94 additions & 23 deletions web/src/beta/components/fields/DateTimeField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";

import Button from "@reearth/beta/components/Button";
import Icon from "@reearth/beta/components/Icon";
import * as Popover from "@reearth/beta/components/Popover";
import Text from "@reearth/beta/components/Text";
import { useT } from "@reearth/services/i18n";
import { styled } from "@reearth/services/theme";

import Property from "..";
import TextInput from "../common/TextInput";

import EditPanel from "./EditPanel";

export type Props = {
name?: string;
Expand All @@ -13,34 +19,60 @@ export type Props = {
};

const DateTimeField: React.FC<Props> = ({ name, description, value, onChange }) => {
const [time, setTime] = useState<string>(value?.split(" ")[1] ?? "HH:MM:SS");
const [date, setDate] = useState<string>(value?.split(" ")[0] ?? "YYYY-MM-DD");
const [open, setOpen] = useState(false);
const t = useT();

const handleTimeChange = useCallback(
(newValue: string | undefined) => {
if (newValue === undefined) return;
const handlePopOver = useCallback(() => setOpen(!open), [open]);
const handleRemoveSetting = useCallback(() => {
if (!value) return;
setDateTime("");
onChange?.();
}, [value, onChange]);

setTime(newValue);
onChange?.(date + " " + newValue);
},
[date, onChange],
);
const [dateTime, setDateTime] = useState(value);

const handleDateChange = useCallback(
(newValue: string | undefined) => {
if (newValue === undefined) return;

setDate(newValue);
onChange?.(newValue + " " + time);
},
[time, onChange],
);
useEffect(() => {
setDateTime(value);
}, [value]);

return (
<Property name={name} description={description}>
<Wrapper>
<TextInput type="date" value={date} onChange={handleDateChange} />
<TextInput type="time" value={time} onChange={handleTimeChange} />
<Popover.Provider open={!!open} placement="bottom-start">
<Popover.Trigger asChild>
<InputWrapper disabled={true}>
<Input dataTimeSet={!!dateTime}>
<Text size="footnote" customColor>
{dateTime ? dateTime : "YYYY-MM-DDThh:mm:ss±hh:mm"}
</Text>
<DeleteIcon
icon="bin"
size={10}
disabled={!dateTime}
onClick={handleRemoveSetting}
/>
</Input>
<TriggerButton
buttonType="secondary"
text={t("set")}
icon="clock"
size="small"
iconPosition="left"
onClick={() => handlePopOver()}
/>
</InputWrapper>
</Popover.Trigger>
<PopoverContent autoFocus={false}>
{open && (
<EditPanel
setDateTime={setDateTime}
value={dateTime}
onChange={onChange}
onClose={handlePopOver}
/>
)}
</PopoverContent>
</Popover.Provider>
</Wrapper>
</Property>
);
Expand All @@ -53,3 +85,42 @@ const Wrapper = styled.div`
align-items: stretch;
gap: 4px;
`;

const InputWrapper = styled.div<{ disabled?: boolean }>`
display: flex;
width: 100%;
gap: 10px;
height: 28px;
`;

const Input = styled.div<{ dataTimeSet?: boolean }>`
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
flex: 1;
padding: 0 8px;
border-radius: 4px;
border: 1px solid ${({ theme }) => theme.outline.weak};
color: ${({ theme }) => theme.content.strong};
background: ${({ theme }) => theme.bg[1]};
box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25) inset;
color: ${({ theme, dataTimeSet }) => (dataTimeSet ? theme.content.strong : theme.content.weak)};
`;

const TriggerButton = styled(Button)`
margin: 0;
`;

const PopoverContent = styled(Popover.Content)`
z-index: 701;
`;
const DeleteIcon = styled(Icon)<{ disabled?: boolean }>`
${({ disabled, theme }) =>
disabled
? `color: ${theme.content.weaker};`
: `:hover {
cursor: pointer;
}`}
`;
Loading

0 comments on commit 466362e

Please sign in to comment.