Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 20 additions & 2 deletions src/api/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,26 @@ export const putApplication = async (shopId: string, noticeId: string, applicati

// νŠΉμ • κ°€κ²Œ νŠΉμ • 곡고 정보
export async function getNoticeById(shopId: string, noticeId: string): Promise<NoticeCard> {
const { data } = await axiosInstance.get<NoticeCard>(`/shops/${shopId}/notices/${noticeId}`);
return data;
const { data } = await axiosInstance.get(`/shops/${shopId}/notices/${noticeId}`);

// API 응닡 -> NoticeCard ν˜•νƒœλ‘œ λ§€ν•‘
const noticeCard: NoticeCard = {
id: data.id,
name: data.shop?.name ?? '',
shopId: data.shop?.id ?? shopId,
address1: data.shop?.address1 ?? '',
hourlyPay: data.hourlyPay ?? 0,
originalHourlyPay: data.originalHourlyPay ?? data.hourlyPay ?? 0,
workhour: data.workhour ?? 0,
startsAt: data.startsAt,
closed: data.closed ?? false,
imageUrl: data.imageUrl ?? '',
description: data.description ?? '',
category: data.category ?? '기타', // λˆ„λ½ ν•„μˆ˜
shopDescription: data.shopDescription ?? '', // λˆ„λ½ ν•„μˆ˜
};

return noticeCard;
}

// νŠΉμ • κ°€κ²Œ νŠΉμ • 곡고 μ§€μ›μž λͺ©λ‘
Expand Down
2 changes: 1 addition & 1 deletion src/components/features/noticeList/noticeListSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface NoticeListSectionProps {
}

const NoticeListSection = ({ q, initialFilters }: NoticeListSectionProps) => {
const { notices, isLoading, isInitialized, error, pagination, fetchNotices,reset, filters } =
const { notices, isLoading, isInitialized, error, pagination, fetchNotices, reset, filters } =
useNotices();

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/badge/StatusBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type StatusType = 'pending' | 'accepted' | 'rejected';

interface StatusBadgeProps {
status: StatusType;
variant: 'employer' | 'employee';
variant: 'employer' | 'employee' | 'guest';
onApprove: () => void;
onReject: () => void;
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/ui/card/post/post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cardLayout, CardStatusVariant } from '@/components/ui/card/card.styles'
import CardBadge from '@/components/ui/card/cardBadge';
import CardImage from '@/components/ui/card/cardImage';
import CardInfo from '@/components/ui/card/cardInfo';
import useAuth from '@/hooks/useAuth';
import { getTime } from '@/lib/utils/dateFormatter';
import { formatNumber } from '@/lib/utils/formatNumber';
import { getNoticeStatus } from '@/lib/utils/getNoticeStatus';
Expand All @@ -18,6 +19,7 @@ const STATUS_LABEL = {
} as const;

const Post = ({ notice }: PostProps) => {
const { user } = useAuth();
const {
id,
hourlyPay,
Expand All @@ -33,7 +35,8 @@ const Post = ({ notice }: PostProps) => {
const status = getNoticeStatus(closed, startsAt);
const { date, startTime, endTime } = getTime(startsAt, workhour);
const statusVariant: CardStatusVariant = status === 'open' ? 'open' : 'inactive';
const href = `/notices/${shopId}/${id}`;
const href =
user && user.shop ? `/employer/shops/${shopId}/notices/${id}` : `/notices/${shopId}/${id}`;

return (
<Link href={href} className={postFrame()} aria-label={`${name} 곡고 μƒμ„Έλ‘œ 이동`}>
Expand Down
11 changes: 5 additions & 6 deletions src/components/ui/table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Pagination } from '@/components/ui';
import { TableRowProps } from '@/components/ui/table/TableRowProps';
import { cn } from '@/lib/utils/cn';
import { UserType } from '@/types/user';
import TableRow from './TableRow';

interface TableProps {
data: TableRowProps[];
tableData: TableRowProps[];
userType: 'employer' | 'employee' | 'guest';
headers: string[];
userType: UserType;
total: number;
limit: number;
offset: number;
Expand All @@ -17,7 +16,7 @@ interface TableProps {
// <Table headers={headers} data={data} userType={type} /> type은 확인이 μ’€ 더 ν•„μš”ν•©λ‹ˆλ‹€

export default function Table({
data,
tableData,
headers,
userType,
total,
Expand Down Expand Up @@ -52,8 +51,8 @@ export default function Table({
</tr>
</thead>
<tbody>
{data.map(row => (
<TableRow key={row.id} rowData={row} variant={userType} />
{tableData.map(row => (
<TableRow key={row.id} {...row} rowData={row} variant={userType} />
))}
</tbody>
</table>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/table/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useState } from 'react';

interface TableTypeVariant {
rowData: TableRowProps;
variant: 'employer' | 'employee';
variant: 'employer' | 'employee' | 'guest';
}

const TD_BASE = 'border-b border-r px-3 py-5 text-base gap-3 md:border-r-0';
Expand Down
Empty file.
233 changes: 178 additions & 55 deletions src/pages/employer/shops/[shopId]/notices/[noticeId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,212 @@
import { getApplications, getNoticeById } from '@/api/applications';
import { Button, Notice, Table } from '@/components/ui';
import { Button, Modal, Notice, Table } from '@/components/ui';
import { TableRowProps } from '@/components/ui/table/TableRowProps';
import useAuth from '@/hooks/useAuth';
import { NoticeCard } from '@/types/notice';
import axiosInstance from '@/lib/axios';
import { getNoticeStatus } from '@/lib/utils/getNoticeStatus';
import { toNoticeCard } from '@/lib/utils/parse';
import type { NoticeCard } from '@/types/notice';
import { Shop } from '@/types/shop';
import { UserRole } from '@/types/user';
import type { GetServerSideProps } from 'next';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';

const EmployerNoticePage = () => {
const router = useRouter();
const { query } = router;
const shopId = Array.isArray(query.shopId) ? query.shopId[0] : query.shopId;
const noticeId = Array.isArray(query.noticeId) ? query.noticeId[0] : query.noticeId;
interface ModalItems {
title?: string;
primaryText?: string;
secondaryText?: string;
onPrimary?: () => void;
onSecondary?: () => void;
}
interface EditItems extends ModalItems {
noShop?: ModalItems;
shop?: ModalItems;
}
interface ApplicationTableApiResponse {
item: {
id: string;
status: string;
user?: {
href: string;
item: {
id: string;
name: string;
bio: string;
phone: string;
};
};
notice?: {
item?: {
startsAt: string;
workhour: number;
hourlyPay?: number;
};
};
};
links: unknown[];
}

const { user } = useAuth();
const EDIT_ITEMS: Record<UserRole, EditItems> = {
guest: {
title: '둜그인이 ν•„μš”ν•©λ‹ˆλ‹€',
primaryText: 'λ‘œκ·ΈμΈν•˜κΈ°',
secondaryText: 'λ‹«κΈ°',
},
employer: {
shop: {},
noShop: {
title: 'λ‚΄ κ°€κ²Œλ₯Ό λ¨Όμ € λ“±λ‘ν•΄μ£Όμ„Έμš”',
primaryText: 'κ°€κ²Œ 등둝',
secondaryText: 'λ‹«κΈ°',
},
},
employee: {
title: 'μ ‘κ·Όν•  수 μ—†μŠ΅λ‹ˆλ‹€',
primaryText: '확인',
},
};

function hasShopFields(user: Shop | null) {
if (!user) return false;
return Boolean(
user.name &&
user.category &&
user.address1 &&
user.address2 &&
user.description &&
user.imageUrl &&
user.originalHourlyPay
);
}

const [notice, setNotice] = useState<NoticeCard>();
export const getServerSideProps: GetServerSideProps<{ notice: NoticeCard }> = async ({
params,
}) => {
const { shopId, noticeId } = params as { shopId: string; noticeId: string };
try {
const noticeRes = await axiosInstance.get(`shops/${shopId}/notices/${noticeId}`);
return { props: { notice: toNoticeCard(noticeRes.data) } };
} catch {
return {
notFound: true,
};
}
};

const [headers, setHeaders] = useState<string[]>([]);
const EmployerNoticeDetailPage = ({ notice }: { notice: NoticeCard }) => {
const headers = ['μ‹ μ²­μž', 'μ†Œκ°œ', 'μ „ν™”λ²ˆν˜Έ', 'μƒνƒœ'];
const [data, setData] = useState<TableRowProps[]>([]);

const [offset, setOffset] = useState(0);
const limit = 5;

// employer만 μ ‘κ·Ό κ°€λŠ₯
useEffect(() => {
if (user && !user.shop) {
router.replace('/');
const { role, isLogin, user } = useAuth();
const router = useRouter();
const [modalOpen, setModalOpen] = useState(false);
const [modal, setModal] = useState<ModalItems | null>(null);

const status = getNoticeStatus(notice.closed, notice.startsAt);
const canEdit = useMemo(() => status === 'open', [status]);

// 곡고 νŽΈμ§‘ν•˜κΈ°
const handleEditClick = useCallback(() => {
if (!canEdit) return;

if (!isLogin) {
const items = EDIT_ITEMS.guest;
setModal({
...items,
onPrimary: () => router.push('/login'),
onSecondary: () => setModalOpen(false),
});
setModalOpen(true);
return;
}
}, [user, router]);

// 곡고 쑰회
useEffect(() => {
if (!shopId || !noticeId) return;
if (role === 'employee') {
const items = EDIT_ITEMS.employee;
setModal({
...items,
onPrimary: () => setModalOpen(false),
});
setModalOpen(true);
return;
}

const fetchNotice = async () => {
const result = await getNoticeById(shopId, noticeId);
setNotice(result);
};
const hasShop = hasShopFields(user?.shop?.item ?? null);
if (!hasShop) {
const items = EDIT_ITEMS.employer.noShop;
setModal({
...items,
onPrimary: () => router.push('/my-shop'),
onSecondary: () => setModalOpen(false),
});
setModalOpen(true);
return;
}

fetchNotice().catch(() => {
setNotice(undefined);
});
}, [shopId, noticeId]);
router.push(`/employer/shops/${notice.shopId}/notices/${notice.id}/edit`);
}, [canEdit, isLogin, role, user, notice, router]);

// μ§€μ›μž λͺ©λ‘ 쑰회
useEffect(() => {
if (!shopId || !noticeId) return;
// μ‹ μ²­μž 뢈러였기

useEffect(() => {
const fetchApplications = async () => {
const applications = await getApplications(shopId, noticeId, offset, limit);
setHeaders(['μ‹ μ²­μž', 'μ†Œκ°œ', 'μ „ν™”λ²ˆν˜Έ', 'μƒνƒœ']);
setData(
applications.map(app => ({
id: app.id,
name: app.user?.name ?? '-',
startsAt: '-',
workhour: 0,
hourlyPay: '-',
status: app.status,
bio: '-',
phone: '-',
}))
const res = await axiosInstance.get<{ items: ApplicationTableApiResponse[] }>(
`/shops/${notice.shopId}/notices/${notice.id}/applications`,
{ params: { offset, limit } }
);

const tableData: TableRowProps[] = res.data.items.map(app => {
const userItem = app.item.user?.item;
const noticeItem = app.item.notice?.item;

return {
id: app.item.id,
name: userItem?.name ?? '-',
bio: userItem?.bio ?? '-',
phone: userItem?.phone ?? '-',
startsAt: noticeItem?.startsAt ?? '-',
workhour: noticeItem?.workhour ?? 0,
hourlyPay: noticeItem?.hourlyPay
? `${noticeItem.hourlyPay.toLocaleString()}원`
: '정보 μ—†μŒ',
status: app.item.status,
};
});

setData(tableData);
};

fetchApplications();
}, [shopId, noticeId, offset]);

if (!notice) return null;
}, [notice.shopId, notice.id, offset, limit]);

return (
<div className='p-4'>
<Notice notice={notice} variant='notice'>
<div>
<Notice notice={notice} className='py-10 tablet:py-16'>
<Button
variant='secondary'
size='md'
className='w-full'
onClick={() => router.push(`/employer/notices/${notice.id}/edit`)}
size='xs38'
full
className='font-bold'
variant={'secondary'}
onClick={handleEditClick}
>
곡고 νŽΈμ§‘ν•˜κΈ°
</Button>
<Modal
open={modalOpen}
onClose={() => setModalOpen(false)}
variant='warning'
title={modal?.title ?? 'μœ μ € 정보λ₯Ό ν™•μΈν•΄μ£Όμ„Έμš”'}
primaryText={modal?.primaryText ?? '확인'}
onPrimary={modal?.onPrimary ?? (() => setModalOpen(false))}
secondaryText={modal?.secondaryText}
onSecondary={modal?.onSecondary}
/>
</Notice>
<Table
headers={headers}
data={data}
tableData={data}
userType='employer'
total={data.length}
limit={limit}
Expand All @@ -94,4 +217,4 @@ const EmployerNoticePage = () => {
);
};

export default EmployerNoticePage;
export default EmployerNoticeDetailPage;
Loading
Loading