Skip to content

Commit

Permalink
feat: create todo modal 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
kumsil1006 committed Nov 29, 2022
1 parent dc26d1e commit fedb21f
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 3 deletions.
7 changes: 6 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { ReactElement } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Provider } from 'jotai';
import styled from 'styled-components';
import { ToastContainer } from 'react-toastify';

import Header from './container/Header';
import Menubar from './container/Menubar';
import Main from '@page/Main';

import Todos from '@page/Todos';
import Main from './page/Main';
import OverLay from '@components/OverLay';

const Wrapper = styled.div`
width: 100%;
Expand All @@ -16,6 +19,8 @@ const App = (): ReactElement => {
return (
<Provider>
<BrowserRouter>
<ToastContainer />
<OverLay />
<Menubar />
<Wrapper>
<Header />
Expand Down
20 changes: 20 additions & 0 deletions client/src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// import { ReactElement, memo, FC } from 'react';
// import styled from 'styled-components';

// interface InputProps {
// type: string;
// width?: string;
// height?: string;
// onChange: Function;
// value?: any;
// }
// const StyledInput = styled.input<InputProps>`
// width: 100%;
// `;

// const Input: FC<InputProps> = ({ value, type, width, height, onChange }: InputProps): ReactElement => {
// console.log('fjadhfdkja');
// return <input value={value} onChange={onChange} type={type} />;
// };

// export default memo(Input);
87 changes: 87 additions & 0 deletions client/src/components/LabeledInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ReactElement, useState, memo } from 'react';
import Text from '@components/Text';
import { PRIMARY_COLORS } from '@util/Constants';
import { getTodayDate } from '@util/Common';

import styled from 'styled-components';
import Select from '@components/Select';
import { toast } from 'react-toastify';

const { darkGray, lightGray } = PRIMARY_COLORS;

interface InputProps {
label: string;
maxLength: number | string;
type: string;
id: string;
}

const Wrapper = styled.div`
width: 100%;
& {
width: 100%;
gap: 5px;
}
> *:focus {
outline: 1px solid ${darkGray};
}
> input,
textarea,
select {
padding: 8px;
border: 1px solid ${lightGray};
border-radius: 5px;
color: ${darkGray};
}
> input[type='text'],
textarea {
&:last-child {
width: 100%;
}
}
> input[type='date'] {
&:last-child {
width: 30%;
}
}
`;

const LabeledInput = ({ label, maxLength, type, id }: InputProps): ReactElement => {
const [input, setInput] = useState('');
const [dateInput, setDateInput] = useState(getTodayDate());

const handleOnChangeText = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { value } = e.target;

if (maxLength === Number.MAX_VALUE || maxLength < 0) {
return setInput(value);
}
if (maxLength > value.length) {
return setInput(value);
}
toast.error('제목은 50자 이상 입력 불가능합니다.');
};

const handleOnChangeDate = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { value } = e.target;

if (getTodayDate() <= value) {
return setDateInput(value);
}

toast.error('새로 생성하는 Todo는 과거로 설정 불가능합니다.');
};

return (
<Wrapper>
<Text text={label} fontFamily={'SanSerif'} fontSize={'18px'} color={darkGray} fontWeight={'500'} />
{type === 'text' && <input value={input} onChange={handleOnChangeText} type={type} id={id} />}
{type === 'textarea' && <textarea id={id} />}
{type === 'select' && <Select options={['A', 'B', 'C']} id={id} />}
{type === 'date' && <input type="date" value={dateInput} id={id} onChange={handleOnChangeDate} />}
</Wrapper>
);
};

export default memo(LabeledInput);
31 changes: 31 additions & 0 deletions client/src/components/OverLay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TABLE_MODALS } from '@util/Constants';
import { modalTypeAtom } from '@util/GlobalState';
import { useAtom } from 'jotai';
import { ReactElement } from 'react';
import styled from 'styled-components';

const StyledOverlay = styled.div`
background-color: rgba(0, 0, 0, 0.7);
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 10;
`;

const { none } = TABLE_MODALS;

const OverLay = (): ReactElement => {
const [modalType, setModalType] = useAtom(modalTypeAtom);

const hanldeOnClick = (): void => {
setModalType(none);
};

return <>{modalType !== none && <StyledOverlay onClick={hanldeOnClick} />}</>;
};

export default OverLay;
28 changes: 28 additions & 0 deletions client/src/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PRIMARY_COLORS } from '@util/Constants';
import { ReactElement, memo } from 'react';
import styled from 'styled-components';

const { lightGray, black } = PRIMARY_COLORS;

const StyledSelect = styled.select`
width: 20%;
border: 1px solid ${lightGray};
border-radius: 5px;
padding: 8px;
color: ${black};
`;

const Select = ({ options, id }: { options: string[]; id: string }): ReactElement => {
return (
<StyledSelect id={id}>
{options.map((value) => {
return (
<option key={value} value={value}>
{value}
</option>
);
})}
</StyledSelect>
);
};
export default memo(Select);
1 change: 1 addition & 0 deletions client/src/container/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Wrapper = styled.div`
height: 10vh;
padding: 25px;
font-family: 'Roboto';
z-index: -10;
`;

const Header = (): ReactElement => {
Expand Down
108 changes: 108 additions & 0 deletions client/src/container/TableModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { memo, ReactElement, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { useAtom } from 'jotai';

import Text from '@components/Text';

import { TABLE_MODALS, PRIMARY_COLORS, MODAL_INPUT_LIST, MODAL_LABEL_ID } from '@util/Constants';
import { modalTypeAtom } from '@util/GlobalState';

import LabeledInput from '@components/LabeledInput';
import Button from '@components/Button';
import { getModalValues } from '@util/Common';

const { create, update, none } = TABLE_MODALS;
const { offWhite, red, blue } = PRIMARY_COLORS;

interface WrapperProps {
ref: any;
}

const Wrapper = styled.div<WrapperProps>`
width: 50vw;
height: 70vh;
left: 21vw;
position: absolute;
background-color: ${offWhite};
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 20px;
z-index: 100;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 30px;
gap: 10px;
`;

const ButtonWrapper = styled.div`
width: 100%;
display: flex;
gap: 15px;
padding-top: 15px;
text-align: right;
justify-content: flex-end;
`;

const TableModal = (): ReactElement => {
const [modalType, setModalType] = useAtom(modalTypeAtom);
const [modalHeader, setModalHeader] = useState('');
const modalWrapper = useRef<HTMLInputElement>();

useEffect(() => {
if (modalType === create) {
return setModalHeader('할 일 추가하기');
}
if (modalType === update) {
return setModalHeader('할 일 수정하기');
}
setModalHeader(none);
}, [modalType]);

const setComplete = (): void => {
let newData = {};

if (modalWrapper.current === undefined) {
return;
}

const userInputs = getModalValues(modalWrapper.current);

userInputs.forEach((item) => {
const { id, value } = item;
newData = { ...newData, [id]: value };
});

console.log(newData);
};

const setCancel = (): void => {
setModalType(none);
};

return (
<Wrapper ref={modalWrapper}>
<Text text={modalHeader} fontFamily={'SanSerif'} fontSize={'24px'} fontWeight={'600'} />
{MODAL_INPUT_LIST.map((item) => {
const { type, label, maxLength } = item;
return (
<LabeledInput
key={`${label}-${type}`}
id={MODAL_LABEL_ID[label as keyof typeof MODAL_LABEL_ID]}
label={label}
maxLength={maxLength}
type={type}
/>
);
})}
<ButtonWrapper>
<Button context={<Text text="취소" color={red} fontWeight={'700'} fontSize={'18px'} />} onClick={setCancel} />
<Button
context={<Text text="확인" color={blue} fontWeight={'700'} fontSize={'18px'} />}
onClick={setComplete}
/>
</ButtonWrapper>
</Wrapper>
);
};

export default memo(TableModal);
15 changes: 15 additions & 0 deletions client/src/hooks/useTextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Text from '@components/Text';
import { useState } from 'react';

const useTextInput = (label: string, maxLength?: number): any[] => {
const [inputText, setInputText] = useState('');

const handleOnChange = ({ target }): void => {
console.log(target);
console.log(inputText.length);
};

return [handleOnChange];
};

export default useTextInput;
18 changes: 18 additions & 0 deletions client/src/images/Create.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 11 additions & 2 deletions client/src/page/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import TodoContents from '@container/TodoContents';

import useTodoList from '../hooks/useTodoList';

import { isFinishedAtom } from '@util/GlobalState';
import { isFinishedAtom, modalTypeAtom } from '@util/GlobalState';
import { TABLE_MODALS } from '@util/Constants';

import 'react-toastify/dist/ReactToastify.css';

Expand All @@ -22,9 +23,18 @@ const Wrapper = styled.div`
align-items: center;
`;

const { none } = TABLE_MODALS;

const Main = (): ReactElement => {
const [, activeTodo] = useTodoList();
const [isFinished] = useAtom(isFinishedAtom);
const [modalType, setModalType] = useAtom(modalTypeAtom);

useEffect(() => {
if (modalType !== none) {
setModalType(none);
}
}, []);

useEffect(() => {
if (isFinished) {
Expand All @@ -44,7 +54,6 @@ const Main = (): ReactElement => {
) : (
<div>Good Job bbb</div>
)}
<ToastContainer />
</Wrapper>
);
};
Expand Down
Loading

0 comments on commit fedb21f

Please sign in to comment.