diff --git a/__tests__/unit/validateTask.test.tsx b/__tests__/unit/validateTask.test.tsx index 018b754..178e7a6 100644 --- a/__tests__/unit/validateTask.test.tsx +++ b/__tests__/unit/validateTask.test.tsx @@ -7,7 +7,9 @@ describe('タスク追加バリデーション', () => { taskId: '01G6NM6EJ7C35J6ZHF6Z3CHGVK', parentTaskId: null, taskName: 'タスク名', + startDate: '2022-09-20', dueDate: '2222-06-29', + assignLabelIdList: [], status: false, } @@ -20,7 +22,9 @@ describe('タスク追加バリデーション', () => { taskId: '1', parentTaskId: null, taskName: 'タスク名', + startDate: '2022-09-20', dueDate: '2222-06-29', + assignLabelIdList: [], status: false, } @@ -33,7 +37,9 @@ describe('タスク追加バリデーション', () => { taskId: '01G6NM6EJ7C35J6ZHF6Z3CHGVK', parentTaskId: null, taskName: '', + startDate: '2022-09-20', dueDate: '2222-06-29', + assignLabelIdList: [], status: false, } const err_msg = validateTask(task) @@ -45,7 +51,9 @@ describe('タスク追加バリデーション', () => { taskId: '01G6NM6EJ7C35J6ZHF6Z3CHGVK', parentTaskId: null, taskName: 'タスク名', - dueDate: '2222-06-29--------', + startDate: '2022-09-20', + dueDate: '2222-06-29-----------------', + assignLabelIdList: [], status: false, } const err_msg = validateTask(task) @@ -57,7 +65,9 @@ describe('タスク追加バリデーション', () => { taskId: '01G6NM6EJ7C35J6ZHF6Z3CHGVK', parentTaskId: null, taskName: 'タスク名', + startDate: '2022-09-20', dueDate: '2000-06-29', + assignLabelIdList: [], status: false, } const err_msg = validateTask(task) diff --git a/package.json b/package.json index ed7ce51..30c8a68 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "next": "12.1.5", "react": "18.1.0", "react-dom": "18.1.0", + "react-draggable": "^4.4.5", "react-error-boundary": "3.1.4", "react-redux": "^8.0.2", "ulid": "^2.3.0" diff --git a/pages/index.tsx b/pages/index.tsx index 69b022b..3a44c3a 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,8 @@ -import { useEffect, FC, ChangeEvent, useState } from 'react' +import { useEffect, FC, useState } from 'react' import { useDispatch } from 'react-redux' +import { Button } from '@mantine/core' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCalendar } from '@fortawesome/free-solid-svg-icons' import { setTaskList } from '../src/stores/TaskListSlice' import { setLabelList } from '../src/stores/LabelListSlice' import { @@ -11,10 +14,12 @@ import TaskAdd from '../src/components/TaskAdd' import TaskClear from '../src/components/TaskClear' import TaskList from '../src/components/TaskList' import LabelAdd from '../src/components/LabelAdd' +import { TaskCalenderList } from '../src/components/TaskCalenderList' const Index: FC = () => { const [searchString, setSearchString] = useState('') const dispatch = useDispatch() + const [isCalenderView, setIsCalenderView] = useState(false) useEffect(() => { const loadedTaskList = getAllTaskListFromLocalStorage() @@ -37,18 +42,40 @@ const Index: FC = () => { -
- setSearchString(e.target.value)} - /> -
+
+ {/** switch calender */} + + + {/** Search */} +
+ setSearchString(e.target.value)} + /> +
+
-
- +
+ {isCalenderView ? ( + + ) : ( + + )}
diff --git a/src/components/ChildrenTaskAdd.tsx b/src/components/ChildrenTaskAdd.tsx index 6b8d9b7..20b2fdd 100644 --- a/src/components/ChildrenTaskAdd.tsx +++ b/src/components/ChildrenTaskAdd.tsx @@ -20,7 +20,8 @@ const ChildrenTaskAdd: FC = (props) => { // local const [opened, setOpened] = useState(false) const [inputTaskName, setInputTaskName] = useState('') - const [inputDate, setInputDate] = useState(new Date()) + const [inputStartDate, setInputStartDate] = useState(new Date()) + const [inputDueDate, setInputDueDate] = useState(new Date()) const dispatch = useDispatch() const addTaskLocal = (e: FormEvent) => { @@ -29,7 +30,8 @@ const ChildrenTaskAdd: FC = (props) => { taskId: ulid(), parentTaskId: props.parentTaskId, taskName: inputTaskName, - dueDate: dayjs(inputDate).format(DateFormat), + startDate: dayjs(inputStartDate).format(DateFormat), + dueDate: dayjs(inputDueDate).format(DateFormat), status: false, assignLabelIdList: [], } @@ -92,13 +94,23 @@ const ChildrenTaskAdd: FC = (props) => { onChange={(e) => setInputTaskName(e.target.value)} /> + + e == null ? setInputStartDate(new Date()) : setInputStartDate(e) + } + /> + - e == null ? setInputDate(new Date()) : setInputDate(e) + e == null ? setInputDueDate(new Date()) : setInputDueDate(e) } /> diff --git a/src/components/TaskAdd.tsx b/src/components/TaskAdd.tsx index 114dad7..5108305 100644 --- a/src/components/TaskAdd.tsx +++ b/src/components/TaskAdd.tsx @@ -19,7 +19,8 @@ const TaskAdd: FC = (props) => { // local const [opened, setOpened] = useState(false) const [inputTaskName, setInputTaskName] = useState('') - const [inputDate, setInputDate] = useState(new Date()) + const [inputStartDate, setInputStartDate] = useState(new Date()) + const [inputDueDate, setInputDueDate] = useState(new Date()) const dispatch = useDispatch() const addTaskLocal = (e: FormEvent) => { @@ -28,10 +29,12 @@ const TaskAdd: FC = (props) => { taskId: ulid(), parentTaskId: props.parentTaskId, taskName: inputTaskName, - dueDate: dayjs(inputDate).format(DateFormat), + startDate: dayjs(inputStartDate).format(DateFormat), + dueDate: dayjs(inputDueDate).format(DateFormat), status: false, assignLabelIdList: [], } + console.warn(newTask) const err_msg = validateTask(newTask) if (err_msg) { showNotification({ @@ -93,13 +96,23 @@ const TaskAdd: FC = (props) => { onChange={(e) => setInputTaskName(e.target.value)} /> + + e == null ? setInputStartDate(new Date()) : setInputStartDate(e) + } + /> + - e == null ? setInputDate(new Date()) : setInputDate(e) + e == null ? setInputDueDate(new Date()) : setInputDueDate(e) } /> diff --git a/src/components/TaskCalenderList.tsx b/src/components/TaskCalenderList.tsx new file mode 100644 index 0000000..495c854 --- /dev/null +++ b/src/components/TaskCalenderList.tsx @@ -0,0 +1,148 @@ +import { FC, useState, useRef, useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../stores/store' +import dayjs from 'dayjs' +import Draggable from 'react-draggable' +import { showNotification } from '@mantine/notifications' +import { updateTask } from '../stores/TaskListSlice' +import type { Task } from '../types/Task' +import { DateFormat, validateTask } from '../functions/Task' + +export const TaskCalenderList: FC = () => { + const taskListSelector = useSelector((state: RootState) => state.taskList) + + const dateLength = 100 + const dateArray = new Array(dateLength) + .fill('') + .map((_, i) => dayjs().add(i, 'day')) + + return ( +
+
+ {dateArray.map((date, index) => ( +
+ {date.get('date') === 1 ? {date.get('month') + 1} : ''} +
+ ))} +
+
+ {dateArray.map((date, index) => ( +
+ {date.get('date')} +
+ ))} +
+ {taskListSelector.taskList.map((task) => ( + + ))} +
+ ) +} + +type Props = { + task: Task +} + +const CalenderListTask: FC = ({ task }) => { + const CALEMDER_DATE_WIDTH = 32 + const [startX, setStartX] = useState(0) + const [marginLeft, setMarginLeft] = useState(0) + const nodeRef = useRef(null) + + useEffect(() => { + const newMarginLeft = + Math.max(dayjs(task.startDate).diff(dayjs(new Date()), 'day') + 1, 0) * + CALEMDER_DATE_WIDTH + setMarginLeft(newMarginLeft) + }, [task.startDate]) + + const handleStart = (e: any) => { + setStartX(e.screenX) + } + const handleStop = (e: any) => { + const diffDays = (e.screenX - startX) / CALEMDER_DATE_WIDTH + updateTaskLocal(diffDays) + } + + const dispatch = useDispatch() + + const updateTaskLocal = (diffDays: number) => { + const newStartDate = dayjs(task.startDate) + .add(diffDays, 'day') + .format(DateFormat) + const newDueDate = dayjs(task.dueDate) + .add(diffDays, 'day') + .format(DateFormat) + const newTask: Task = { + ...task, + startDate: newStartDate, + dueDate: newDueDate, + } + + const msg = validateTask(newTask) + if (msg) { + showNotification({ + title: 'タスクの編集に失敗しました', + message: msg, + autoClose: 5000, + color: 'red', + }) + return + } + + dispatch(updateTask(newTask)) + showNotification({ + message: 'タスクの編集に成功しました', + autoClose: 5000, + color: 'green', + }) + } + + return ( + { + handleStart(e) + }} + defaultPosition={{ x: 0, y: 0 }} + position={{ x: 0, y: 0 }} + onStop={(e: any) => { + handleStop(e) + }} + nodeRef={nodeRef} + > +
+
+ {dayjs(task.dueDate).diff(dayjs(task.startDate), 'day') + 1 <= 3 + ? `${task.startDate.substring(5, 10)} ~ ${task.dueDate.substring( + 5, + 10 + )}` + : `${task.startDate} ~ ${task.dueDate}`} +
+
+ {task.taskName} +
+
+
+ ) +} diff --git a/src/components/TaskListTask.tsx b/src/components/TaskListTask.tsx index d2e1b3a..b12074e 100644 --- a/src/components/TaskListTask.tsx +++ b/src/components/TaskListTask.tsx @@ -142,7 +142,7 @@ const TaskListTask: FC = (props) => {
- 締め切り: {props.task.dueDate} + 開始:{props.task.startDate} ~ 締め切り:{props.task.dueDate}
diff --git a/src/functions/Task.ts b/src/functions/Task.ts index bb981d2..a12b6d3 100644 --- a/src/functions/Task.ts +++ b/src/functions/Task.ts @@ -25,13 +25,20 @@ export function validateTask(task: Task): string { const currentDate = dayjs() const dueDate = dayjs(task.dueDate) - if (!validateDate(task.dueDate, DateFormat)) { + if ( + !validateDate(task.dueDate, DateFormat) || + !validateDate(task.startDate, DateFormat) + ) { return '期限が不正です' } if (!isAfterOrSameByDate(currentDate, dueDate)) { return '締め切り日は未来の日付にしてください' } + if (task.startDate > task.dueDate) { + return '開始日は締め切り日より前の日付にしてください' + } + //check if all parent tasks dueDate is before dueDate of current task const parentTasks = getAllParentTasks({ task: task, diff --git a/src/types/Task.ts b/src/types/Task.ts index b8e8f0a..bd2ca6f 100644 --- a/src/types/Task.ts +++ b/src/types/Task.ts @@ -39,6 +39,7 @@ type Task = { taskId: string // ulid parentTaskId: ParentTaskIdType taskName: string + startDate: string dueDate: string status: ChangeStatus assignLabelIdList: string[] diff --git a/yarn.lock b/yarn.lock index 19c41d9..2af4189 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5880,6 +5880,14 @@ react-dom@18.1.0: loose-envify "^1.1.0" scheduler "^0.22.0" +react-draggable@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c" + integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== + dependencies: + clsx "^1.1.1" + prop-types "^15.8.1" + react-error-boundary@3.1.4: version "3.1.4" resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz"