Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/components/contents/homework-detail-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
useDeletePeerReview,
useUpdatePeerReview,
} from '@/hooks/queries/peer-review-api';
import { useIsLeader } from '@/stores/useLeaderStore';

import { useUserStore } from '@/stores/useUserStore';
import DeleteHomeworkModal from '../modals/delete-homework-modal';
import EditHomeworkModal from '../modals/edit-homework-modal';
Expand Down Expand Up @@ -152,11 +152,8 @@ function PeerReviewSection({
peerReviews,
isMyHomework,
}: PeerReviewSectionProps) {
const currentUserId = useUserStore((state) => state.memberId);
const isMissionCreator = useIsLeader(currentUserId);

// 자기 과제가 아니고, 미션 생성자(리더)가 아닌 경우에만 리뷰 작성 가능
const canWriteReview = !isMyHomework && !isMissionCreator;
// 자기 과제가 아닌 경우에만 리뷰 작성 가능 (리더도 허용)
const canWriteReview = !isMyHomework;
const [reviewText, setReviewText] = useState('');
const { mutate: createPeerReview, isPending } = useCreatePeerReview();

Expand Down
15 changes: 11 additions & 4 deletions src/components/filtering/study-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,26 @@ function FilterDropdown({

const hasSelection = selected.length > 0;

const selectedLabels = selected
.map((v) => options.find((option) => option.value === v)?.label)
.filter(Boolean)
.join(', ');

const displayLabel = hasSelection ? `${label}: ${selectedLabels}` : label;

return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<button
type="button"
className={[
'h-500ems-center flex gap-50 rounded-full border px-200 py-100',
'flex items-center gap-50 rounded-full border px-200 py-100 whitespace-nowrap',
hasSelection
? 'border-border-brand bg-fill-brand-subtle-default text-text-brand'
: 'border-border-default bg-fill-neutral-subtle-default text-text-default',
].join(' ')}
>
<span className="font-designer-14m">{label}</span>
<span className="font-designer-14m">{displayLabel}</span>
{open ? (
<ChevronUp className="size-4" />
) : (
Expand Down Expand Up @@ -178,7 +185,7 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) {
!values.recruiting; // recruiting이 false면 필터 적용 중

return (
<div className="flex items-center gap-100">
<div className="flex flex-wrap items-center gap-100">
<FilterDropdown
label="스터디 유형"
options={STUDY_TYPE_OPTIONS}
Expand All @@ -187,7 +194,7 @@ export default function StudyFilter({ values, onChange }: StudyFilterProps) {
/>

<FilterDropdown
label="포지션"
label="직무"
options={POSITION_OPTIONS}
selected={values.targetRoles}
onChange={handleTargetRolesChange}
Expand Down
5 changes: 5 additions & 0 deletions src/components/modals/create-evaluation-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useCreateEvaluation,
useGetMissionEvaluationGrades,
} from '@/hooks/queries/evaluation-api';
import { useScrollToNextField } from '@/hooks/use-scroll-to-next-field';
import { useToastStore } from '@/stores/use-toast-store';
import { TextAreaInput } from '../ui/input';

Expand Down Expand Up @@ -85,6 +86,7 @@ function CreateEvaluationForm({

const { handleSubmit, formState } = methods;

const scrollToNext = useScrollToNextField();
const { data: grades } = useGetMissionEvaluationGrades();
const { mutate: createEvaluation } = useCreateEvaluation();
const showToast = useToastStore((state) => state.showToast);
Expand Down Expand Up @@ -127,6 +129,8 @@ function CreateEvaluationForm({
label="평가 점수 선택"
direction="vertical"
required
scrollable
onAfterChange={() => scrollToNext('gradeCode')}
>
<GroupItems
variant="square"
Expand All @@ -141,6 +145,7 @@ function CreateEvaluationForm({
label="정성 코멘트"
direction="vertical"
required
scrollable
>
<TextAreaInput
id="comment"
Expand Down
161 changes: 161 additions & 0 deletions src/components/modals/inquiry-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { XIcon } from 'lucide-react';
import { FormProvider, useForm } from 'react-hook-form';
import Button from '@/components/ui/button';
import { BaseInput, TextAreaInput } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal';
import {
INQUIRY_CONTENT_MAX_LENGTH,
inquirySchema,
InquiryCategory,
InquiryFormValues,
INQUIRY_TITLE_MAX_LENGTH,
} from '@/features/study/group/model/inquiry.schema';
import { useCreateInquiry } from '@/hooks/queries/inquiry-api';
import { useScrollToNextField } from '@/hooks/use-scroll-to-next-field';
import { useToastStore } from '@/stores/use-toast-store';
import { SingleDropdown } from '../ui/dropdown';
import FormField from '../ui/form/form-field';

const INQUIRY_CATEGORY_OPTIONS = [
{ value: InquiryCategory.CURRICULUM, label: '커리큘럼' },
{ value: InquiryCategory.DIFFICULTY, label: '난이도' },
{ value: InquiryCategory.HW_AMOUNT, label: '과제량' },
{ value: InquiryCategory.ETC, label: '기타' },
] as const;

interface InquiryModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
studyId: number;
}

export default function InquiryModal({
open,
onOpenChange,
studyId,
}: InquiryModalProps) {
const showToast = useToastStore((state) => state.showToast);
const { mutate: createInquiry, isPending } = useCreateInquiry();

const form = useForm<InquiryFormValues>({
resolver: zodResolver(inquirySchema),
defaultValues: {
title: '',
content: '',
category: undefined,
},
});

const { handleSubmit, reset } = form;
const scrollToNext = useScrollToNextField();

const onSubmit = (data: InquiryFormValues) => {
createInquiry(
{
groupStudyId: studyId,
request: {
title: data.title,
content: data.content,
category: data.category,
},
},
{
onSuccess: () => {
showToast('문의가 성공적으로 제출되었습니다.', 'success');
reset();
onOpenChange(false);
},
onError: (error) => {
showToast('문의 제출에 실패했습니다. 다시 시도해주세요.', 'error');
console.error('문의 제출 오류:', error);
},
},
);
};

const handleOpenChange = (isOpen: boolean) => {
if (!isOpen) {
reset();
}
onOpenChange(isOpen);
};

return (
<Modal.Root open={open} onOpenChange={handleOpenChange}>
<Modal.Portal>
<Modal.Overlay />
<Modal.Content size="medium" className="w-[500px]">
<Modal.Header className="border-border-default flex items-center justify-between border-b">
<Modal.Title className="font-designer-20b text-text-strong">
스터디 문의하기
</Modal.Title>
<Modal.Close>
<XIcon />
</Modal.Close>
</Modal.Header>
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<Modal.Body className="flex flex-col gap-300">
<FormField<InquiryFormValues, 'category'>
name="category"
label="문의 종류"
direction="vertical"
required
scrollable
onAfterChange={() => scrollToNext('category')}
>
<SingleDropdown
options={INQUIRY_CATEGORY_OPTIONS}
placeholder="선택해주세요"
/>
</FormField>
<FormField<InquiryFormValues, 'title'>
name="title"
label="제목"
direction="vertical"
required
scrollable
onAfterBlurFilled={() => scrollToNext('title')}
>
<BaseInput
maxLength={INQUIRY_TITLE_MAX_LENGTH}
placeholder="제목을 입력하세요"
hideMeta={false}
/>
</FormField>
<FormField<InquiryFormValues, 'content'>
name="content"
label="내용"
direction="vertical"
required
scrollable
>
<TextAreaInput
placeholder="내용을 입력하세요"
maxLength={INQUIRY_CONTENT_MAX_LENGTH}
className="font-designer-16m text-text-default h-auto min-h-[150px]"
/>
</FormField>
</Modal.Body>
<Modal.Footer className="flex justify-end gap-100">
<Button
type="button"
color="secondary"
onClick={() => handleOpenChange(false)}
>
취소
</Button>
<Button type="submit" color="primary" disabled={isPending}>
{isPending ? '제출 중...' : '문의 제출'}
</Button>
</Modal.Footer>
</form>
</FormProvider>
</Modal.Content>
</Modal.Portal>
</Modal.Root>
);
}
87 changes: 46 additions & 41 deletions src/components/modals/submit-homework-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { FormProvider, useForm, useFormContext } from 'react-hook-form';
import { z } from 'zod';
import Button from '@/components/ui/button';
import FormField from '@/components/ui/form/form-field';
Expand Down Expand Up @@ -28,37 +28,50 @@ export default function SubmitHomeworkModal({
onSuccess,
}: SubmitHomeworkModalProps) {
const [open, setOpen] = useState<boolean>(false);
const methods = useForm<SubmitHomeworkFormValues>({
resolver: zodResolver(SubmitHomeworkFormSchema),
mode: 'onChange',
defaultValues: {
textContent: '',
attachmentLink: '',
},
});

return (
<Modal.Root open={open} onOpenChange={setOpen}>
<Modal.Trigger asChild>
<Button
size="medium"
className="font-designer-16m w-fit"
color="primary"
>
과제 제출하기
</Button>
</Modal.Trigger>

<Modal.Portal>
<Modal.Overlay />
<Modal.Content className="w-[840px]">
<Modal.Header variant="form">
<Modal.Title className="font-designer-20b text-text-strong">
과제 제출하기
</Modal.Title>
<Modal.CloseButton onClick={() => setOpen(false)} />
</Modal.Header>

<SubmitHomeworkForm
missionId={missionId}
onClose={() => setOpen(false)}
onSuccess={onSuccess}
/>
</Modal.Content>
</Modal.Portal>
</Modal.Root>
<FormProvider {...methods}>
<Modal.Root open={open} onOpenChange={setOpen}>
<Modal.Trigger asChild>
<Button
size="medium"
className="font-designer-16m w-fit"
color="primary"
>
과제 제출하기
</Button>
</Modal.Trigger>

<Modal.Portal>
<Modal.Overlay />
<Modal.Content className="w-[840px]">
<Modal.Header variant="form">
<Modal.Title className="font-designer-20b text-text-strong">
과제 제출하기
</Modal.Title>
<Modal.CloseButton onClick={() => setOpen(false)} />
</Modal.Header>

<SubmitHomeworkForm
missionId={missionId}
onClose={() => setOpen(false)}
onSuccess={() => {
methods.reset();
onSuccess?.();
}}
/>
</Modal.Content>
</Modal.Portal>
</Modal.Root>
</FormProvider>
);
}

Expand All @@ -73,15 +86,7 @@ function SubmitHomeworkForm({
onClose,
onSuccess,
}: SubmitHomeworkFormProps) {
const methods = useForm<SubmitHomeworkFormValues>({
resolver: zodResolver(SubmitHomeworkFormSchema),
mode: 'onChange',
defaultValues: {
textContent: '',
attachmentLink: '',
},
});

const methods = useFormContext<SubmitHomeworkFormValues>();
const { handleSubmit, formState } = methods;

const { mutate: submitHomework } = useSubmitHomework();
Expand Down Expand Up @@ -110,7 +115,7 @@ function SubmitHomeworkForm({
};

return (
<FormProvider {...methods}>
<>
<Modal.Body variant="form">
<form
id="submit-homework"
Expand Down Expand Up @@ -163,6 +168,6 @@ function SubmitHomeworkForm({
제출하기
</Button>
</Modal.Footer>
</FormProvider>
</>
);
}
Loading
Loading