Skip to content

Commit

Permalink
feat(my-recruit): 내 공고 페이지 카드 UI구현
Browse files Browse the repository at this point in the history
  • Loading branch information
qkrdmstlr3 committed Aug 12, 2024
1 parent d7f716b commit f1fd5a9
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/app/(sidebar)/my-recruit/containers/AllRecruitment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import { motion } from 'framer-motion';
import { color } from '@/system/token/color';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { cardList } from '../mock';
import { RowCard } from './components/Card/RowCard';

export function AllRecruitment() {
return (
Expand Down Expand Up @@ -43,6 +45,12 @@ export function AllRecruitment() {
<span className="text-neutral-30 text-label1">등록된 공고가 없어요</span>
</motion.button>
</Dialog.Trigger>

<div className="flex flex-col gap-[12px]">
{cardList.map((cardInfo) => (
<RowCard key={`${cardInfo.period}-${cardInfo.title}`} {...cardInfo} />
))}
</div>
</>
);
}
19 changes: 19 additions & 0 deletions src/app/(sidebar)/my-recruit/containers/ProgressingRecruitment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { ShoeIcon } from './components/ShoeIcon';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { motion } from 'framer-motion';
import { color } from '@/system/token/color';
import { cardList } from '../mock';
import { BoxCard } from './components/Card/BoxCard';
import { TouchButton } from '@/components/TouchButton';

export function ProgressingRecruitment() {
return (
Expand All @@ -21,6 +24,22 @@ export function ProgressingRecruitment() {
<span className="text-neutral-30 text-label1">진행중인 공고가 없어요</span>
</motion.button>
</Dialog.Trigger>

<div className="flex gap-[16px]">
{cardList.map((cardInfo) => (
<BoxCard key={`${cardInfo.period}-${cardInfo.title}`} {...cardInfo} />
))}
</div>

<Spacing size={32} />
<div className="flex justify-center">
<TouchButton className="px-12 py-8 bg-neutral-5 rounded-[6px] text-caption1 text-neutral-50">
더보기
</TouchButton>
<TouchButton className="px-12 py-8 bg-neutral-5 rounded-[6px] text-caption1 text-neutral-50">
간략히 보기
</TouchButton>
</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Spacing } from '@/components/Spacing';
import { Icon } from '@/system/components';
import { color } from '@/system/token/color';
import { dday } from '@/utils/date';
import { MoreButton } from '@/app/(sidebar)/my-recruit/containers/components/Card/common/MoreButton';
import { StatusButton } from '@/app/(sidebar)/my-recruit/containers/components/Card/common/StatusButton';
import { Dialog } from '@/system/components/Dialog/ShadcnDialog';
import { DueDateDialog } from '../DueDateDialog';

export type ProgressingCardType = {
type: '서류 마감' | '1차 면접' | '2차 면접';
status: '지원 완료' | '서류 통과' | '서류 탈락';
dueDate: Date | null;
period: string;
title: string;
};

export function BoxCard({ type, title, status, dueDate, period }: ProgressingCardType) {
return (
<div className="w-[320px] rounded-[10px] overflow-hidden cursor-pointer">
<div className="h-38 pr-12 pl-20 bg-neutral-95 flex justify-between items-center">
{dueDate == null ? (
<Dialog>
<Dialog.Trigger className="flex justify-between items-center w-full">
<span className="text-label2 text-neutral-50">공고 일정을 등록해주세요</span>
<Icon name="add" size={24} color={color.neutral50} />
</Dialog.Trigger>
<Dialog.Content className="w-400">
<DueDateDialog title={title} />
</Dialog.Content>
</Dialog>
) : (
<>
<div className="flex items-center gap-[4px]">
<Icon name="clover" size={20} color={color.mint30} />
<span className="text-white text-label2 ">
{type} D-{dday(dueDate)}
</span>
</div>
<MoreButton />
</>
)}
</div>
<div className="p-20 pt-16 bg-white border-neutral-5 border-1 rounded-b-[10px] hover:border-neutral-95">
<div className="flex justify-between items-center">
<div className="bg-mint-1 text-mint-50 text-label1 px-8 py-4 rounded-4">{period}</div>
<StatusButton currentStatus={status} />
</div>
<Spacing size={16} />
<span className="text-nuetral-95 font-semibold font-body1 text-ellipsis line-clamp-1">{title}</span>
</div>
</div>
);
}

const statusList = [
{ variant: 'text', text: '지원 준비' },
{ variant: 'text', text: '지원 완료' },
{ variant: 'border' },
{ variant: 'text', text: '서류 통과' },
{ variant: 'text', text: '서류 탈락' },
{ variant: 'border' },
{ variant: 'text', text: '면접 통과' },
{ variant: 'text', text: '면접 탈락' },
{ variant: 'border' },
{ variant: 'text', text: '최종 합격' },
{ variant: 'text', text: '최종 탈락' },
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { If } from '@/components/If';
import { Spacing } from '@/components/Spacing';
import { Icon } from '@/system/components';
import { color } from '@/system/token/color';
import { dday } from '@/utils/date';
import { MoreButton } from '@/app/(sidebar)/my-recruit/containers/components/Card/common/MoreButton';
import { StatusButton } from './common/StatusButton';

interface RowCardProps {
type: '서류 마감' | '1차 면접' | '2차 면접';
status: '지원 완료' | '서류 통과' | '서류 탈락';
dueDate: Date | null;
period: string;
title: string;
}

export function RowCard({ type, title, status, dueDate, period }: RowCardProps) {
return (
<div className="rounded-[10px] overflow-hidden flex cursor-pointer">
<div className="w-12 bg-neutral-95" />
<div className="px-24 py-22 flex-1 flex items-center border-1 border-neutral-5 border-l-neutral-95 rounded-r-[10px] justify-between">
<div className="flex items-center">
<span className="text-neutral-50 text-label2 font-medium">{period}</span>
<Spacing size={24} direction="row" />
<If condition={dueDate != null}>
<div className="px-6 py-4 rounded-[4px] bg-neutral-95 flex gap-[2px]">
<Icon name="clover" size={20} color={color.mint30} />
<span className="text-white text-label2 ">
{type} D-{dday(dueDate!)}
</span>
</div>
</If>
<Spacing size={12} direction="row" />
<span className="font-semibold font-body1">{title}</span>
</div>
<div className="flex items-center">
<StatusButton currentStatus={status} />
<Spacing size={32} direction="row" />
<MoreButton />
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Icon } from '@/system/components';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/system/components/DropdownMenu/DropdownMenu';
import { color } from '@/system/token/color';

export function MoreButton() {
return (
<DropdownMenu>
<DropdownMenuTrigger className="outline-none">
<Icon name="more" size={24} color={color.neutral40} />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem className="gap-[8px]">
<Icon name="delete" color="#FF5C5C" />
<div className="text-red-50 text-[15px] font-normal">삭제하기</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { SwitchCase } from '@/components/SwitchCase';
import { Icon } from '@/system/components';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '@/system/components/DropdownMenu/DropdownMenu';
import { color } from '@/system/token/color';
import { cn } from '@/utils';

interface Props {
currentStatus: string;
}

export function StatusButton({ currentStatus }: Props) {
return (
<DropdownMenu>
<DropdownMenuTrigger className="outline-none">
<div className="flex items-center">
<span className="text-label2 text-neutral-35">{currentStatus}</span>
<Icon name="downChevron" color={color.neutral10} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
{statusList.map((item) => (
<SwitchCase
value={item.variant}
caseBy={{
text:
item.variant === 'text' ? (
<DropdownMenuItem className={cn(currentStatus === item.text ? 'text-neutral-30' : '')}>
{item.text}
</DropdownMenuItem>
) : null,
border: <DropdownMenuSeparator />,
}}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

const statusList = [
{ variant: 'text', text: '지원 준비' },
{ variant: 'text', text: '지원 완료' },
{ variant: 'border' },
{ variant: 'text', text: '서류 통과' },
{ variant: 'text', text: '서류 탈락' },
{ variant: 'border' },
{ variant: 'text', text: '면접 통과' },
{ variant: 'text', text: '면접 탈락' },
{ variant: 'border' },
{ variant: 'text', text: '최종 합격' },
{ variant: 'text', text: '최종 탈락' },
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Spacing } from '@/components/Spacing';
import { Button, Icon } from '@/system/components';
import { color } from '@/system/token/color';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { Popover, PopoverContent, PopoverTrigger } from '@/system/components/Popover/Popover';
import { Calendar } from '@/system/components/Calendar/Calendar';
import { format } from 'date-fns/format';
import { useState } from 'react';

interface DueDateDialogProps {
title: string;
}

export function DueDateDialog({ title }: DueDateDialogProps) {
const [selectedDate, setSelectedDate] = useState<Date>();

const isDateSelected = selectedDate != null;

return (
<div className="p-20">
<div className="flex items-center w-314">
<Icon name="folderFill" size={16} color={color.neutral95} />
<Spacing size={4} direction="row" />
<span className="text-body1 font-semibold flex-1 overflow-hidden text-ellipsis line-clamp-1">{title}</span>
<span className="text-body1">의 공고 일정 등록하기</span>
</div>
<Spacing size={4} />
<span className="text-caption1 text-neutral-35">일정을 등록하면 잊지 않도록 알려드릴게요!</span>
<Spacing size={24} />
{/* 마감일 입력 */}
<div className="w-full flex justify-between items-center p-8 bg-neutral-1 rounded-[8px]">
<span className="text-label1 text-neutral-95">서류마감</span>
<Popover>
<PopoverTrigger>
<motion.div
initial="initial"
variants={{
initial: { backgroundColor: 'rgb(241, 242, 243, 0)' },
touch: { scale: 0.96, transition: { duration: 0.1 } },
hover: { backgroundColor: 'rgb(241, 242, 243, 1)' },
}}
whileTap="touch"
whileHover="hover"
className="px-8 py-4 flex items-center gap-[4px] rounded-[4px]">
<Icon name={isDateSelected ? 'calendarFill' : 'calendar'} size={20} color="#AEB0B6" />
<span className={clsx('text-label2', isDateSelected ? 'text-neutral-95' : 'text-neutral-40')}>
{isDateSelected ? format(selectedDate, 'yyyy.mm.dd') : '마감일을 선택해주세요'}
</span>
</motion.div>
</PopoverTrigger>
<PopoverContent className="w-200">
<Calendar mode="single" selected={selectedDate} onSelect={setSelectedDate} />
</PopoverContent>
</Popover>
</div>
<Spacing size={16} />
<Button className="w-full h-46 flex justify-center items-center gap-[4px] border-[1px] border-neutral-35 border-dashed rounded-[6px]">
<Icon name="add" color={color.neutral35} />
<span className="text-caption1 font-medium text-neutral-35">일정 추가하기</span>
</Button>
</div>
);
}
18 changes: 18 additions & 0 deletions src/app/(sidebar)/my-recruit/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ProgressingCardType } from './containers/components/Card/BoxCard';

export const cardList: ProgressingCardType[] = [
{
type: '1차 면접',
status: '서류 통과',
dueDate: null,
period: '2024 상반기',
title: '디프만 15기 디자이너 직군',
},
{
type: '2차 면접',
status: '지원 완료',
dueDate: new Date('2024-08-20'),
period: '2024 하반기',
title: '2024 네이버 프로덕트 디자이너 신입공채 지원서 제출',
},
];
17 changes: 17 additions & 0 deletions src/components/SwitchCase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface Props<Case extends string | number> {
caseBy: Partial<Record<Case, JSX.Element | null>>;
value: Case;
defaultComponent?: JSX.Element | null;
}

export function SwitchCase<Case extends string | number>({
value,
caseBy,
defaultComponent: defaultComponent = null,
}: Props<Case>) {
if (value == null) {
return defaultComponent;
}

return caseBy[value] ?? defaultComponent;
}
6 changes: 6 additions & 0 deletions src/system/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,23 @@ import { Link } from './SVG/Link';
import { Unlink } from './SVG/Unlink';
import { Calendar } from './SVG/Calendar';
import { CalendarFill } from './SVG/CalendarFill';
import { Clover } from './SVG/Clover';
import { DownChevron } from './SVG/DownChevron';
import { FolderFill } from './SVG/FolderFill';

const iconMap = {
bell: Bell,
copy: Copy,
check: Check,
division: Division,
folder: Folder,
folderFill: FolderFill,
logout: Logout,
memo: Memo,
profile: Profile,
profileFill: ProfileFill,
rightChevron: RightChevron,
downChevron: DownChevron,
search: Search,
setting: Setting,
down: Down,
Expand All @@ -45,6 +50,7 @@ const iconMap = {
unlink: Unlink,
calendar: Calendar,
calendarFill: CalendarFill,
clover: Clover,
} as const;

export interface IconProps extends IconBaseType {
Expand Down
Loading

0 comments on commit f1fd5a9

Please sign in to comment.