diff --git a/src/app/[slug]/[date]/page.tsx b/src/app/[slug]/[date]/page.tsx index e66bb19..98c2bca 100644 --- a/src/app/[slug]/[date]/page.tsx +++ b/src/app/[slug]/[date]/page.tsx @@ -42,7 +42,7 @@ export default async function Page({ params }: Props) { )}
- +
diff --git a/src/app/_actions/taskActions.ts b/src/app/_actions/taskActions.ts new file mode 100644 index 0000000..a4f81fc --- /dev/null +++ b/src/app/_actions/taskActions.ts @@ -0,0 +1,26 @@ +'use server'; + +import { API_TASK } from '../_constants/apiUrls'; +import { apiPost } from '~/libs/apiClient'; +import { Task } from '~/domains/Task'; + +export const postTask = async ({ + body, + title, + dueDate, + objectiveId, +}: { + body: string; + title: string; + dueDate: Date; + objectiveId: string; +}) => { + return await apiPost<{ task: Task }>(API_TASK(), { + body: JSON.stringify({ + body, + title, + dueDate, + objectiveId, + }), + }); +}; diff --git a/src/app/_components/domains/Nippo/NippoEditor/NippoEditor.tsx b/src/app/_components/domains/Nippo/NippoEditor/NippoEditor.tsx index 6b9570d..f88f9c9 100644 --- a/src/app/_components/domains/Nippo/NippoEditor/NippoEditor.tsx +++ b/src/app/_components/domains/Nippo/NippoEditor/NippoEditor.tsx @@ -3,7 +3,7 @@ import { FC, useCallback } from 'react'; import { postNippo } from '~/app/_actions/nippoActions'; import { Nippo } from '~/domains/Nippo'; -import { Editor } from '~/app/_components/uiParts/Editor'; +import { DebounceEditor } from '~/app/_components/uiParts/Editor/DebounceEditor'; type Props = { objectiveId: string; @@ -19,5 +19,5 @@ export const NippoEditor: FC = ({ objectiveId, nippo, date }) => { [date, objectiveId], ); - return ; + return ; }; diff --git a/src/app/_components/domains/Task/CreateTaskModal/CreateTaskModal.tsx b/src/app/_components/domains/Task/CreateTaskModal/CreateTaskModal.tsx index 9a41969..c6b9cd1 100644 --- a/src/app/_components/domains/Task/CreateTaskModal/CreateTaskModal.tsx +++ b/src/app/_components/domains/Task/CreateTaskModal/CreateTaskModal.tsx @@ -1,28 +1,89 @@ 'use client'; -import { FC, useCallback } from 'react'; -import { Input, Modal, ModalBody, ModalContent } from '@nextui-org/react'; +import { FC, useCallback, useState } from 'react'; +import { Button, Input, Modal, ModalBody, ModalContent, ModalFooter } from '@nextui-org/react'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { TaskEditor } from '../TaskEditor'; -import { Task } from '~/domains/Task'; +import { postTask } from '~/app/_actions/taskActions'; type Props = { isOpen: boolean; onOpenChange: () => void; - currentTask?: Task; + objectiveId: string; }; -export const CreateModal: FC = ({ isOpen, onOpenChange, currentTask }) => { +interface IFormInput { + title: string; + body: string; +} + +export const CreateModal: FC = ({ isOpen, onOpenChange, objectiveId }) => { + const [isLoading, setIsLoading] = useState(false); + + const { control, formState, getValues, handleSubmit, reset } = useForm({ + defaultValues: { + title: '', + body: '', + }, + }); + + console.log(getValues().body); + const handleOpenChange = useCallback(() => { + reset(); onOpenChange(); - }, [onOpenChange]); + }, [onOpenChange, reset]); + + const onSubmit: SubmitHandler = useCallback( + async (inputData) => { + if (isLoading) return; + setIsLoading(true); + postTask({ title: inputData.title, body: inputData.body, dueDate: new Date(), objectiveId }) + .catch((error) => { + // TODO: 本来はコンソールに出すのではなく、ユーザーにエラーを通知する + console.error(error); + }) + .then(() => { + handleOpenChange(); + }) + .finally(() => { + setIsLoading(false); + }); + }, + [handleOpenChange, isLoading, objectiveId], + ); return ( - - + ( + + )} + /> + field.onChange(body)} />} + /> + + + ); diff --git a/src/app/_components/domains/Task/TaskEditor/TaskEditor.tsx b/src/app/_components/domains/Task/TaskEditor/TaskEditor.tsx index d7f6008..3898019 100644 --- a/src/app/_components/domains/Task/TaskEditor/TaskEditor.tsx +++ b/src/app/_components/domains/Task/TaskEditor/TaskEditor.tsx @@ -1,17 +1,12 @@ 'use client'; -import { FC, useCallback } from 'react'; +import { FC } from 'react'; import { Editor } from '~/app/_components/uiParts/Editor'; -import { Task } from '~/domains/Task'; type Props = { - task?: Task; + onChangeText: (body: string) => Promise; }; -export const TaskEditor: FC = ({ task }) => { - const handleEditorChange = useCallback(async (body: string) => { - console.log(body); - }, []); - - return ; +export const TaskEditor: FC = ({ onChangeText }) => { + return ; }; diff --git a/src/app/_components/domains/Task/TaskList/TaskList.tsx b/src/app/_components/domains/Task/TaskList/TaskList.tsx index b04c476..538a932 100644 --- a/src/app/_components/domains/Task/TaskList/TaskList.tsx +++ b/src/app/_components/domains/Task/TaskList/TaskList.tsx @@ -5,7 +5,11 @@ import { FC } from 'react'; import { CreateModal } from '../CreateTaskModal'; import { Icon } from '~/app/_components/uiParts/icons'; -export const TaskList: FC = () => { +type Props = { + objectiveId: string; +}; + +export const TaskList: FC = ({ objectiveId }) => { const { isOpen, onOpen, onOpenChange } = useDisclosure(); return ( @@ -17,7 +21,7 @@ export const TaskList: FC = () => { - + ); }; diff --git a/src/app/_components/uiParts/Editor/DebounceEditor.tsx b/src/app/_components/uiParts/Editor/DebounceEditor.tsx new file mode 100644 index 0000000..ae7b14b --- /dev/null +++ b/src/app/_components/uiParts/Editor/DebounceEditor.tsx @@ -0,0 +1,31 @@ +'use client'; + +import './styles.scss'; + +import { useEffect, useState, FC, useCallback } from 'react'; +import { Editor } from './Editor'; +import { useDebounce } from '~/libs/useDebounce'; + +type Props = { + body?: string; + placeholder: string; + onChange: (body: string) => Promise; +}; + +export const DebounceEditor: FC = ({ body, placeholder, onChange }) => { + const [inputText, setInputText] = useState(); + const debouncedInputText = useDebounce({ value: inputText, delay: 200 }); + + const handleEditorChange = useCallback( + async (body: string) => { + await onChange(body); + }, + [onChange], + ); + + useEffect(() => { + debouncedInputText && handleEditorChange(debouncedInputText); + }, [debouncedInputText, handleEditorChange]); + + return setInputText(body)} />; +}; diff --git a/src/app/_components/uiParts/Editor/Editor.tsx b/src/app/_components/uiParts/Editor/Editor.tsx index 6472a2c..fab84e9 100644 --- a/src/app/_components/uiParts/Editor/Editor.tsx +++ b/src/app/_components/uiParts/Editor/Editor.tsx @@ -4,36 +4,21 @@ import './styles.scss'; import { Color } from '@tiptap/extension-color'; import { Link } from '@tiptap/extension-link'; -import { useEffect, useState, FC, useCallback } from 'react'; +import { FC } from 'react'; import { EditorContent, useEditor } from '@tiptap/react'; import TextStyle from '@tiptap/extension-text-style'; import Placeholder from '@tiptap/extension-placeholder'; import StarterKit from '@tiptap/starter-kit'; import ListItem from '@tiptap/extension-list-item'; import Heading from '@tiptap/extension-heading'; -import { useDebounce } from '~/libs/useDebounce'; type Props = { body?: string; placeholder: string; - onChange: (body: string) => Promise; + onChange: (body: string) => void; }; export const Editor: FC = ({ body, placeholder, onChange }) => { - const [inputText, setInputText] = useState(); - const debouncedInputText = useDebounce({ value: inputText, delay: 200 }); - - const handleEditorChange = useCallback( - async (body: string) => { - await onChange(body); - }, - [onChange], - ); - - useEffect(() => { - debouncedInputText && handleEditorChange(debouncedInputText); - }, [debouncedInputText, handleEditorChange]); - const extensions = [ Color.configure({ types: [TextStyle.name, ListItem.name] }), Link.configure(), @@ -60,7 +45,7 @@ export const Editor: FC = ({ body, placeholder, onChange }) => { content: body, autofocus: 'end', onUpdate: ({ editor }) => { - setInputText(editor.getHTML()); + onChange(editor.getHTML()); }, editorProps: { attributes: { diff --git a/src/app/_constants/apiUrls.ts b/src/app/_constants/apiUrls.ts index 23c6b6a..90e791b 100644 --- a/src/app/_constants/apiUrls.ts +++ b/src/app/_constants/apiUrls.ts @@ -8,3 +8,6 @@ export const API_OBJECTIVE_ME = () => `/api/objectives/me`; export const API_OBJECTIVE_ID_NIPPO = (_id: string) => `/api/objectives/${_id}/nippos`; export const API_NIPPO_BY_DATE = (slug: string, data: string) => `/api/nippos/by-date?date=${data}&slug=${slug}`; + +export const API_TASK = () => `/api/tasks`; +export const API_TASK_ID = (id: string) => `/api/tasks/${id}`;