Skip to content

Commit

Permalink
タスクの作成ができる
Browse files Browse the repository at this point in the history
  • Loading branch information
itizawa committed Dec 19, 2023
1 parent 0491240 commit 2a30032
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/app/[slug]/[date]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default async function Page({ params }: Props) {
)}
</div>
<div className="w-[100%] md:w-[200px]">
<TaskList />
<TaskList objectiveId={objective._id} />
</div>
</div>
</div>
Expand Down
26 changes: 26 additions & 0 deletions src/app/_actions/taskActions.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
});
};
4 changes: 2 additions & 2 deletions src/app/_components/domains/Nippo/NippoEditor/NippoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,5 +19,5 @@ export const NippoEditor: FC<Props> = ({ objectiveId, nippo, date }) => {
[date, objectiveId],
);

return <Editor body={nippo?.body} onChange={handleEditorChange} placeholder="振り返りを記入しましょう!" />;
return <DebounceEditor body={nippo?.body} onChange={handleEditorChange} placeholder="振り返りを記入しましょう!" />;
};
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ isOpen, onOpenChange, currentTask }) => {
interface IFormInput {
title: string;
body: string;
}

export const CreateModal: FC<Props> = ({ isOpen, onOpenChange, objectiveId }) => {
const [isLoading, setIsLoading] = useState(false);

const { control, formState, getValues, handleSubmit, reset } = useForm<IFormInput>({
defaultValues: {
title: '',
body: '',
},
});

console.log(getValues().body);

const handleOpenChange = useCallback(() => {
reset();
onOpenChange();
}, [onOpenChange]);
}, [onOpenChange, reset]);

const onSubmit: SubmitHandler<IFormInput> = 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 (
<Modal isOpen={isOpen} onOpenChange={handleOpenChange} placement="center" hideCloseButton size="xl">
<ModalContent>
<ModalBody className="my-[8px]">
<Input defaultValue={currentTask?.title} placeholder="タスクの名前" variant="underlined" size="lg" />
<TaskEditor task={currentTask} />
<Controller
name="title"
control={control}
rules={{ required: true }}
render={({ field, fieldState }) => (
<Input
{...field}
placeholder="タスクの名前"
isInvalid={fieldState.isDirty && field.value.length === 0}
errorMessage={fieldState.isDirty && field.value.length === 0 && 'タイトルを入力してください'}
variant="underlined"
size="lg"
/>
)}
/>
<Controller
name="body"
control={control}
rules={{ required: true, minLength: 1 }}
render={({ field }) => <TaskEditor onChangeText={async (body) => field.onChange(body)} />}
/>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={handleSubmit(onSubmit)} isLoading={isLoading} isDisabled={!formState.isValid}>
タスクを作成
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
Expand Down
13 changes: 4 additions & 9 deletions src/app/_components/domains/Task/TaskEditor/TaskEditor.tsx
Original file line number Diff line number Diff line change
@@ -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<void>;
};

export const TaskEditor: FC<Props> = ({ task }) => {
const handleEditorChange = useCallback(async (body: string) => {
console.log(body);
}, []);

return <Editor body={task?.body} onChange={handleEditorChange} placeholder="タスクの内容を記入しましょう" />;
export const TaskEditor: FC<Props> = ({ onChangeText }) => {
return <Editor onChange={onChangeText} placeholder="タスクの内容を記入しましょう" />;
};
8 changes: 6 additions & 2 deletions src/app/_components/domains/Task/TaskList/TaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props> = ({ objectiveId }) => {
const { isOpen, onOpen, onOpenChange } = useDisclosure();

return (
Expand All @@ -17,7 +21,7 @@ export const TaskList: FC = () => {
<Button className="mt-[8px]" size="sm" color="primary" variant="light" startContent={<Icon icon="PLUS" />} onClick={() => onOpen()}>
タスクの追加
</Button>
<CreateModal isOpen={isOpen} onOpenChange={onOpenChange} />
<CreateModal isOpen={isOpen} onOpenChange={onOpenChange} objectiveId={objectiveId} />
</div>
);
};
31 changes: 31 additions & 0 deletions src/app/_components/uiParts/Editor/DebounceEditor.tsx
Original file line number Diff line number Diff line change
@@ -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<void>;
};

export const DebounceEditor: FC<Props> = ({ body, placeholder, onChange }) => {
const [inputText, setInputText] = useState<string>();
const debouncedInputText = useDebounce({ value: inputText, delay: 200 });

const handleEditorChange = useCallback(
async (body: string) => {
await onChange(body);
},
[onChange],
);

useEffect(() => {
debouncedInputText && handleEditorChange(debouncedInputText);
}, [debouncedInputText, handleEditorChange]);

return <Editor body={body} placeholder={placeholder} onChange={(body) => setInputText(body)} />;
};
21 changes: 3 additions & 18 deletions src/app/_components/uiParts/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
onChange: (body: string) => void;
};

export const Editor: FC<Props> = ({ body, placeholder, onChange }) => {
const [inputText, setInputText] = useState<string>();
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(),
Expand All @@ -60,7 +45,7 @@ export const Editor: FC<Props> = ({ body, placeholder, onChange }) => {
content: body,
autofocus: 'end',
onUpdate: ({ editor }) => {
setInputText(editor.getHTML());
onChange(editor.getHTML());
},
editorProps: {
attributes: {
Expand Down
3 changes: 3 additions & 0 deletions src/app/_constants/apiUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;

0 comments on commit 2a30032

Please sign in to comment.