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;
}