diff --git a/frontend/src/components/_common/atoms/OpenInNewTab/style.ts b/frontend/src/components/_common/atoms/OpenInNewTab/style.ts index aa4c67e8e..f6419fee5 100644 --- a/frontend/src/components/_common/atoms/OpenInNewTab/style.ts +++ b/frontend/src/components/_common/atoms/OpenInNewTab/style.ts @@ -1,15 +1,9 @@ import styled from '@emotion/styled'; const Container = styled.div` - width: 100%; - display: flex; align-items: center; - gap: 0.8rem; - - padding: 0.8rem; - - border-bottom: 1px solid ${({ theme }) => theme.baseColors.grayscale[500]}; + gap: 0.4rem; & > svg { color: ${({ theme }) => theme.baseColors.purplescale[800]}; diff --git a/frontend/src/components/dashboard/DashboardHeader/DashboardHeader.stories.tsx b/frontend/src/components/dashboard/DashboardHeader/DashboardHeader.stories.tsx new file mode 100644 index 000000000..8081cff6f --- /dev/null +++ b/frontend/src/components/dashboard/DashboardHeader/DashboardHeader.stories.tsx @@ -0,0 +1,43 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { withRouter } from 'storybook-addon-remix-react-router'; +import DashboardHeader from '.'; + +const meta = { + title: 'Organisms/Dashboard/DashboardHeader', + component: DashboardHeader, + parameters: { + layout: 'centered', + docs: { + description: { + component: '현재 공고의 대시보드 헤더를 표시하는 컴포넌트입니다.', + }, + }, + }, + tags: ['autodocs'], + argTypes: { + title: { control: 'text' }, + postUrl: { control: 'text' }, + startDate: { control: 'date' }, + endDate: { control: 'date' }, + }, + decorators: [ + withRouter, + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const DashboardHeaderDefault: Story = { + args: { + title: '우아한테크코스 7기 프론트엔드 모집', + postUrl: 'https://www.cruru.kr', + startDate: '2024-07-15T09:00:00Z', + endDate: '2024-12-15T09:00:00Z', + }, +}; diff --git a/frontend/src/components/dashboard/DashboardHeader/index.tsx b/frontend/src/components/dashboard/DashboardHeader/index.tsx new file mode 100644 index 000000000..30ce4a5cd --- /dev/null +++ b/frontend/src/components/dashboard/DashboardHeader/index.tsx @@ -0,0 +1,40 @@ +import formatDate from '@utils/formatDate'; +import { HiOutlineCalendar } from 'react-icons/hi'; + +import OpenInNewTab from '@components/_common/atoms/OpenInNewTab'; +import RecruitmentStatusFlag from '@components/recruitment/RecruitmentStatusFlag'; + +import S from './style'; + +interface DashboardHeaderProps { + title: string; + postUrl: string; + startDate: string; + endDate: string; +} + +export default function DashboardHeader({ title, postUrl, startDate, endDate }: DashboardHeaderProps) { + return ( + + + + {title} + + + + + + {`${formatDate(startDate)} ~ ${formatDate(endDate)}`} + + + + + + ); +} diff --git a/frontend/src/components/dashboard/DashboardHeader/style.ts b/frontend/src/components/dashboard/DashboardHeader/style.ts new file mode 100644 index 000000000..1cfccbdea --- /dev/null +++ b/frontend/src/components/dashboard/DashboardHeader/style.ts @@ -0,0 +1,57 @@ +import styled from '@emotion/styled'; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + padding-bottom: 1.2rem; +`; + +const TitleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; +`; + +const Title = styled.div` + ${({ theme }) => theme.typography.heading[700]} + + display: flex; + align-items: baseline; + gap: 1.2rem; +`; + +const RecruitmentStatusContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 1.6rem; +`; + +const RecruitmentPeriod = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 0.6rem; + + ${({ theme }) => theme.typography.heading[400]}; + color: ${({ theme }) => theme.baseColors.grayscale[600]}; +`; + +const PostLinkContainer = styled.div` + display: flex; + gap: 0.8rem; + min-width: 30rem; + height: fit-content; +`; + +const S = { + Wrapper, + TitleContainer, + Title, + RecruitmentStatusContainer, + RecruitmentPeriod, + PostLinkContainer, +}; + +export default S; diff --git a/frontend/src/components/recruitment/RecruitmentCard/index.tsx b/frontend/src/components/recruitment/RecruitmentCard/index.tsx index a657b3e42..24f219ad4 100644 --- a/frontend/src/components/recruitment/RecruitmentCard/index.tsx +++ b/frontend/src/components/recruitment/RecruitmentCard/index.tsx @@ -1,9 +1,8 @@ import { HiOutlineClock } from 'react-icons/hi'; -import { RecruitmentStatusType } from '@customTypes/recruitment'; import formatDate from '@utils/formatDate'; -import { getTimeStatus } from '@utils/compareTime'; import RecruitmentCardStat from '../RecruitmentCardStat'; +import RecruitmentStatusFlag from '../RecruitmentStatusFlag'; import S from './style'; interface RecruitmentStats { @@ -29,12 +28,6 @@ const POST_STATS_KEY: Record = { total: '전체', } as const; -const RECRUITMENT_STATUS: Record = { - Pending: '모집 예정', - Ongoing: '모집 진행 중', - Closed: '모집 마감', -}; - export default function RecruitmentCard({ dashboardId, title, @@ -43,8 +36,6 @@ export default function RecruitmentCard({ endDate, onClick, }: RecruitmentCardProps) { - const { status } = getTimeStatus({ startDate, endDate }); - const postStatsMap: [string, number][] = [ [POST_STATS_KEY.total, postStats.total], [POST_STATS_KEY.inProgress, postStats.inProgress], @@ -56,7 +47,10 @@ export default function RecruitmentCard({ {title} - {RECRUITMENT_STATUS[status]} + {formatDate(endDate)} diff --git a/frontend/src/components/recruitment/RecruitmentStatusFlag/RecruitmentStatusFlag.stories.tsx b/frontend/src/components/recruitment/RecruitmentStatusFlag/RecruitmentStatusFlag.stories.tsx new file mode 100644 index 000000000..cb9fc0794 --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentStatusFlag/RecruitmentStatusFlag.stories.tsx @@ -0,0 +1,30 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import RecruitmentStatusFlag from '.'; + +const meta = { + title: 'Organisms/Recruitment/RecruitmentStatusFlag', + component: RecruitmentStatusFlag, + parameters: { + layout: 'centered', + docs: { + description: { + component: '현재 공고의 모집 진행 상태를 표시하는 플래그 컴포넌트입니다.', + }, + }, + }, + tags: ['autodocs'], + argTypes: { + startDate: { control: 'date' }, + endDate: { control: 'date' }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const RecruitmentStatusFlagDefault: Story = { + args: { + startDate: '2024-07-15T09:00:00Z', + endDate: '2024-12-15T09:00:00Z', + }, +}; diff --git a/frontend/src/components/recruitment/RecruitmentStatusFlag/index.tsx b/frontend/src/components/recruitment/RecruitmentStatusFlag/index.tsx new file mode 100644 index 000000000..f5cb5246e --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentStatusFlag/index.tsx @@ -0,0 +1,20 @@ +import { RecruitmentStatusType } from '@customTypes/recruitment'; +import { getTimeStatus } from '@utils/compareTime'; +import S from './style'; + +interface RecruitmentStatusFlagProps { + startDate: string; + endDate: string; +} + +const RECRUITMENT_STATUS: Record = { + Pending: '모집 예정', + Ongoing: '모집 진행 중', + Closed: '모집 마감', +}; + +export default function RecruitmentStatusFlag({ startDate, endDate }: RecruitmentStatusFlagProps) { + const { status } = getTimeStatus({ startDate, endDate }); + + return {RECRUITMENT_STATUS[status]}; +} diff --git a/frontend/src/components/recruitment/RecruitmentStatusFlag/style.ts b/frontend/src/components/recruitment/RecruitmentStatusFlag/style.ts new file mode 100644 index 000000000..a9df8b87c --- /dev/null +++ b/frontend/src/components/recruitment/RecruitmentStatusFlag/style.ts @@ -0,0 +1,38 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/react'; +import { RecruitmentStatusType } from '@customTypes/recruitment'; + +const FlagContainer = styled.div<{ status: RecruitmentStatusType }>` + width: fit-content; + border-radius: 0.4rem; + padding: 0.5rem 0.8rem; + ${({ theme }) => theme.typography.common.small}; + + ${({ theme, status }) => { + if (status === 'Pending') { + return css` + border: 1px solid ${theme.baseColors.bluescale[100]}; + background: ${theme.baseColors.bluescale[50]}; + color: ${theme.baseColors.bluescale[500]}; + `; + } + if (status === 'Ongoing') { + return css` + border: 1px solid ${theme.baseColors.purplescale[100]}; + background: ${theme.baseColors.purplescale[50]}; + color: ${theme.baseColors.purplescale[500]}; + `; + } + return css` + border: 1px solid ${theme.baseColors.grayscale[300]}; + background: ${theme.baseColors.grayscale[200]}; + color: ${theme.baseColors.grayscale[700]}; + `; + }} +`; + +const S = { + FlagContainer, +}; + +export default S; diff --git a/frontend/src/hooks/useProcess/index.ts b/frontend/src/hooks/useProcess/index.ts index 37633e522..c1d5484a2 100644 --- a/frontend/src/hooks/useProcess/index.ts +++ b/frontend/src/hooks/useProcess/index.ts @@ -24,6 +24,8 @@ interface UseProcessReturn { error: Error | null; isLoading: boolean; postUrl: string; + startDate: string; + endDate: string; } export default function useProcess({ dashboardId, applyFormId }: UseProcessProps): UseProcessReturn { @@ -42,5 +44,7 @@ export default function useProcess({ dashboardId, applyFormId }: UseProcessProps processList, error, isLoading, + startDate: data?.startDate ?? '0', + endDate: data?.endDate ?? '0', }; } diff --git a/frontend/src/mocks/processMockData.json b/frontend/src/mocks/processMockData.json index 1d7b47dd0..a157a051a 100644 --- a/frontend/src/mocks/processMockData.json +++ b/frontend/src/mocks/processMockData.json @@ -1,6 +1,8 @@ { "title": "우아한테크코스 프론트엔드 크루 모집", "applyFormId": "1", + "startDate": "2024-09-21T00:00:00", + "endDate": "2024-12-24T00:00:00", "processes": [ { "processId": 1, diff --git a/frontend/src/pages/Dashboard/index.tsx b/frontend/src/pages/Dashboard/index.tsx index 1b6b6334f..c65b4df05 100644 --- a/frontend/src/pages/Dashboard/index.tsx +++ b/frontend/src/pages/Dashboard/index.tsx @@ -6,8 +6,7 @@ import ProcessBoard from '@components/dashboard/ProcessBoard'; import ApplyManagement from '@components/applyManagement'; import ProcessManageBoard from '@components/processManagement/ProcessManageBoard'; import PostManageBoard from '@components/postManagement/PostManageBoard'; -import OpenInNewTab from '@components/_common/atoms/OpenInNewTab'; -import CopyToClipboard from '@components/_common/atoms/CopyToClipboard'; +import DashboardHeader from '@components/dashboard/DashboardHeader'; import useTab from '@components/_common/molecules/Tab/useTab'; import useProcess from '@hooks/useProcess'; @@ -24,23 +23,18 @@ export type DashboardTabItems = '지원자 관리' | '모집 과정 관리' | ' export default function Dashboard() { const { dashboardId, applyFormId } = useParams() as { dashboardId: string; applyFormId: string }; - const { processes, title, postUrl } = useProcess({ dashboardId, applyFormId }); + const { processes, title, postUrl, startDate, endDate } = useProcess({ dashboardId, applyFormId }); const { currentMenu, moveTab } = useTab({ defaultValue: '지원자 관리' }); return ( - - {title} - - - - - - + {Object.values(DASHBOARD_TAB_MENUS).map((label) => ( diff --git a/frontend/src/pages/Dashboard/style.ts b/frontend/src/pages/Dashboard/style.ts index 137d1d94c..839c1e2e3 100644 --- a/frontend/src/pages/Dashboard/style.ts +++ b/frontend/src/pages/Dashboard/style.ts @@ -6,22 +6,6 @@ const AppContainer = styled.div` height: 100%; `; -const Header = styled.div` - display: flex; - justify-content: space-between; -`; - -const Title = styled.div` - ${({ theme }) => theme.typography.heading[700]} -`; - -const CopyWrapper = styled.div` - display: flex; - gap: 0.8rem; - - min-width: 30rem; -`; - const DashboardPanel = styled.div<{ isVisible: boolean }>` width: 100%; height: 80%; @@ -38,10 +22,7 @@ const DashboardPanel = styled.div<{ isVisible: boolean }>` const S = { AppContainer, - Header, - Title, DashboardPanel, - CopyWrapper, }; export default S; diff --git a/frontend/src/types/process.ts b/frontend/src/types/process.ts index 0520d8577..be64d78ae 100644 --- a/frontend/src/types/process.ts +++ b/frontend/src/types/process.ts @@ -19,4 +19,6 @@ export interface ProcessResponse { applyFormId: string; title: string; processes: Process[]; + startDate: string; + endDate: string; }