feat: feedback edit/delete buttons and author name on edit
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,7 +44,7 @@ router.post('/:taskId', async (req, res, next) => {
|
|||||||
// PATCH /api/details/item/:id
|
// PATCH /api/details/item/:id
|
||||||
router.patch('/item/:id', async (req, res, next) => {
|
router.patch('/item/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { content } = req.body as Record<string, string>;
|
const { content, authorName } = req.body as Record<string, string>;
|
||||||
if (!content?.toString().trim()) throw new AppError(400, '피드백 내용은 필수입니다.');
|
if (!content?.toString().trim()) throw new AppError(400, '피드백 내용은 필수입니다.');
|
||||||
|
|
||||||
const existing = await prisma.taskDetail.findUnique({ where: { id: req.params.id } });
|
const existing = await prisma.taskDetail.findUnique({ where: { id: req.params.id } });
|
||||||
@@ -52,7 +52,10 @@ router.patch('/item/:id', async (req, res, next) => {
|
|||||||
|
|
||||||
const detail = await prisma.taskDetail.update({
|
const detail = await prisma.taskDetail.update({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
data: { content: content.toString().trim() },
|
data: {
|
||||||
|
content: content.toString().trim(),
|
||||||
|
...(authorName !== undefined && { authorName: authorName?.toString().trim() || null }),
|
||||||
|
},
|
||||||
include: { author: { select: { id: true, name: true } } },
|
include: { author: { select: { id: true, name: true } } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -66,18 +66,18 @@ export function FeedbackModal({
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{mode === 'add' && (
|
<label className="block">
|
||||||
<label className="block">
|
<span className="mb-1 block text-sm font-bold text-slate-500">
|
||||||
<span className="mb-1 block text-sm font-bold text-slate-500">이름 *</span>
|
이름 {mode === 'add' ? '*' : ''}
|
||||||
<input
|
</span>
|
||||||
required
|
<input
|
||||||
value={form.authorName}
|
required={mode === 'add'}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, authorName: e.target.value }))}
|
value={form.authorName}
|
||||||
className="w-full rounded-lg border border-slate-200 px-3 py-2 text-base focus:border-emerald-400 focus:outline-none"
|
onChange={(e) => setForm((p) => ({ ...p, authorName: e.target.value }))}
|
||||||
placeholder="작성자 이름"
|
className="w-full rounded-lg border border-slate-200 px-3 py-2 text-base focus:border-emerald-400 focus:outline-none"
|
||||||
/>
|
placeholder="작성자 이름"
|
||||||
</label>
|
/>
|
||||||
)}
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 border-t border-slate-100 px-6 py-4">
|
<div className="flex justify-end gap-2 border-t border-slate-100 px-6 py-4">
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ function DetailView({ task }: { task: TaskWithRelations }) {
|
|||||||
} else if (feedbackModal?.detail) {
|
} else if (feedbackModal?.detail) {
|
||||||
await apiClient.patch(`/details/item/${feedbackModal.detail.id}`, {
|
await apiClient.patch(`/details/item/${feedbackModal.detail.id}`, {
|
||||||
content: data.content.trim(),
|
content: data.content.trim(),
|
||||||
|
authorName: data.authorName.trim() || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await qc.invalidateQueries({ queryKey: ['task', task.id] });
|
await qc.invalidateQueries({ queryKey: ['task', task.id] });
|
||||||
@@ -516,23 +517,45 @@ function DetailView({ task }: { task: TaskWithRelations }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sortedFeedbacks.length === 0 ? (
|
{sortedFeedbacks.length === 0 ? (
|
||||||
<p className="text-lg text-slate-400">우클릭 또는 + 버튼으로 피드백을 추가하세요.</p>
|
<p className="text-lg text-slate-400">+ 버튼 또는 빈 곳 우클릭으로 피드백을 추가하세요.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="mb-2 min-h-0 flex-1 space-y-2">
|
<div className="mb-2 min-h-0 flex-1 space-y-2">
|
||||||
{sortedFeedbacks.map((f) => (
|
{sortedFeedbacks.map((f) => (
|
||||||
<div
|
<div
|
||||||
key={f.id}
|
key={f.id}
|
||||||
className="rounded-lg bg-slate-50 px-3 py-2"
|
className="flex items-start justify-between gap-2 rounded-lg bg-slate-50 px-3 py-2"
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setFeedbackCtx({ x: e.clientX, y: e.clientY, detailId: f.id });
|
setFeedbackCtx({ x: e.clientX, y: e.clientY, detailId: f.id });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p className="truncate text-2xl font-black leading-snug text-slate-700">
|
<p className="min-w-0 flex-1 truncate text-2xl font-black leading-snug text-slate-700">
|
||||||
{f.content}
|
{f.content}
|
||||||
<span className="font-bold text-slate-400"> — {feedbackAuthorName(f)}</span>
|
<span className="font-bold text-slate-400"> — {feedbackAuthorName(f)}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex shrink-0 items-center gap-1 pt-0.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="피드백 수정"
|
||||||
|
onClick={() => setFeedbackModal({ mode: 'edit', detail: f })}
|
||||||
|
className="rounded px-2 py-0.5 text-xs font-bold text-slate-500 hover:bg-slate-100"
|
||||||
|
>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="피드백 삭제"
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm('이 피드백을 삭제하시겠습니까?')) {
|
||||||
|
deleteFeedback.mutate(f.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="rounded px-2 py-0.5 text-xs font-bold text-red-400 hover:bg-red-50 hover:text-red-600"
|
||||||
|
>
|
||||||
|
🗑
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user