Skip to content

Commit ec48694

Browse files
authored
Merge pull request #26 from CodeitPart3/COMPONENT-15-HONG
[feat] 공통 컴포넌트 구현 (Post)
2 parents 6d375c2 + b5ef46d commit ec48694

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
"@tailwindcss/postcss": "^4.1.4",
2525
"@tailwindcss/vite": "^4.1.4",
2626
"axios": "^1.8.4",
27+
"clsx": "^2.1.1",
2728
"postcss": "^8.5.3",
2829
"react": "^19.0.0",
2930
"react-dom": "^19.0.0",
3031
"react-router-dom": "^7.5.1",
32+
"tailwind-merge": "^3.2.0",
3133
"tailwindcss": "^4.1.4"
3234
},
3335
"devDependencies": {

src/assets/icon/arrow-up-bold.svg

Lines changed: 3 additions & 0 deletions
Loading

src/components/Post/Post.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { cn } from "@/utils/cn";
2+
import { Link } from "react-router-dom";
3+
import { formatTimeRange, isPastDate } from "@/utils/datetime";
4+
import IconTime from "@/assets/icon/time.svg?react";
5+
import IconLocation from "@/assets/icon/location.svg?react";
6+
import IconArrow from "@/assets/icon/arrow-up.svg?react";
7+
import IconArrowBold from "@/assets/icon/arrow-up-bold.svg?react";
8+
9+
const getPayRateText = (
10+
hourlyPay: number,
11+
originalPay: number,
12+
): {
13+
rawRate: number;
14+
displayRate: number;
15+
rateText: string;
16+
} => {
17+
const rawRate = ((hourlyPay - originalPay) / originalPay) * 100;
18+
const displayRate = Math.min(Math.round(rawRate), 100);
19+
const rateText = `기존 시급보다 ${displayRate}%`;
20+
21+
return { rawRate, displayRate, rateText };
22+
};
23+
24+
interface PostProps {
25+
name: string;
26+
imageUrl: string;
27+
address1: string;
28+
originalHourlyPay: number;
29+
link: string;
30+
hourlyPay: number;
31+
startsAt: string;
32+
workhour: number;
33+
closed: boolean;
34+
}
35+
36+
export default function Post({
37+
name,
38+
imageUrl,
39+
address1,
40+
originalHourlyPay,
41+
link,
42+
hourlyPay,
43+
startsAt,
44+
workhour,
45+
closed,
46+
}: PostProps) {
47+
const timeRange = formatTimeRange(startsAt, workhour);
48+
const isPast = isPastDate(startsAt, workhour);
49+
const isDimmed = closed || isPast;
50+
const { displayRate, rateText } = getPayRateText(
51+
hourlyPay,
52+
originalHourlyPay,
53+
);
54+
55+
return (
56+
<Link to={link} className="no-underline text-inherit block">
57+
<article
58+
className={cn(
59+
"relative flex w-full flex-col rounded-xl border border-gray-20 bg-white p-3 shadow-md transition hover:shadow-lg md:p-4",
60+
)}
61+
>
62+
<div className="relative">
63+
<img
64+
src={imageUrl}
65+
alt={name}
66+
className="w-full h-[84px] overflow-hidden rounded-xl bg-cover bg-center md:h-40"
67+
/>
68+
{isDimmed && (
69+
<h3 className="absolute inset-0 flex items-center justify-center rounded-md bg-black/70 text-sm text-gray-30 md:text-[28px]">
70+
{closed ? "마감 완료" : "지난 공고"}
71+
</h3>
72+
)}
73+
</div>
74+
75+
<div className={cn(isDimmed && "opacity-20")}>
76+
<div className="mt-3 flex flex-col gap-2 md:mt-5">
77+
<h3 className="text-base md:text-xl">{name}</h3>
78+
<p className="flex items-start gap-[6px] text-xs font-normal text-gray-50 md:text-[14px] md:leading-[22px]">
79+
<IconTime className="h-4 w-4 md:h-5 md:w-5" />
80+
{timeRange}
81+
</p>
82+
<p className="flex items-start gap-[6px] text-xs font-normal text-gray-50 md:text-[14px] md:leading-[22px]">
83+
<IconLocation className="h-4 w-4 md:h-5 md:w-5" />
84+
{address1}
85+
</p>
86+
</div>
87+
88+
<div className="mt-4 flex flex-col items-start md:flex-row md:items-center md:justify-between">
89+
<h2 className="text-lg md:text-2xl">
90+
{hourlyPay.toLocaleString()}
91+
</h2>
92+
93+
{displayRate > 0 && (
94+
<span
95+
className={cn(
96+
"mt-[5px] flex items-center gap-[2px] text-xs font-normal text-red-40 md:text-sm md:text-white md:px-3 md:py-2 md:rounded-[20px]",
97+
displayRate >= 50
98+
? "md:bg-red-40"
99+
: displayRate >= 25
100+
? "md:bg-red-30"
101+
: "md:bg-red-20",
102+
)}
103+
>
104+
{rateText}
105+
<IconArrow className="hidden h-5 w-5 md:block" />
106+
<IconArrowBold className="h-4 w-4 md:hidden" />
107+
</span>
108+
)}
109+
</div>
110+
</div>
111+
</article>
112+
</Link>
113+
);
114+
}

src/utils/cn.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ClassValue, clsx } from "clsx";
2+
import { twMerge } from "tailwind-merge";
3+
4+
export const cn = (...inputs: ClassValue[]) => {
5+
return twMerge(clsx(inputs));
6+
};

0 commit comments

Comments
 (0)