diff --git a/package-lock.json b/package-lock.json index fbf05ce..1473347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,12 @@ "@tailwindcss/postcss": "^4.1.4", "@tailwindcss/vite": "^4.1.4", "axios": "^1.8.4", + "clsx": "^2.1.1", "postcss": "^8.5.3", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.5.1", + "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4" }, "devDependencies": { @@ -2601,6 +2603,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6343,6 +6354,16 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", diff --git a/package.json b/package.json index ad10591..28e1196 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "@tailwindcss/postcss": "^4.1.4", "@tailwindcss/vite": "^4.1.4", "axios": "^1.8.4", + "clsx": "^2.1.1", "postcss": "^8.5.3", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.5.1", + "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4" }, "devDependencies": { diff --git a/src/assets/icon/arrow-up-bold.svg b/src/assets/icon/arrow-up-bold.svg new file mode 100644 index 0000000..c399ddd --- /dev/null +++ b/src/assets/icon/arrow-up-bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Post/Post.tsx b/src/components/Post/Post.tsx new file mode 100644 index 0000000..6f495cf --- /dev/null +++ b/src/components/Post/Post.tsx @@ -0,0 +1,114 @@ +import { cn } from "@/utils/cn"; +import { Link } from "react-router-dom"; +import { formatTimeRange, isPastDate } from "@/utils/datetime"; +import IconTime from "@/assets/icon/time.svg?react"; +import IconLocation from "@/assets/icon/location.svg?react"; +import IconArrow from "@/assets/icon/arrow-up.svg?react"; +import IconArrowBold from "@/assets/icon/arrow-up-bold.svg?react"; + +const getPayRateText = ( + hourlyPay: number, + originalPay: number, +): { + rawRate: number; + displayRate: number; + rateText: string; +} => { + const rawRate = ((hourlyPay - originalPay) / originalPay) * 100; + const displayRate = Math.min(Math.round(rawRate), 100); + const rateText = `기존 시급보다 ${displayRate}%`; + + return { rawRate, displayRate, rateText }; +}; + +interface PostProps { + name: string; + imageUrl: string; + address1: string; + originalHourlyPay: number; + link: string; + hourlyPay: number; + startsAt: string; + workhour: number; + closed: boolean; +} + +export default function Post({ + name, + imageUrl, + address1, + originalHourlyPay, + link, + hourlyPay, + startsAt, + workhour, + closed, +}: PostProps) { + const timeRange = formatTimeRange(startsAt, workhour); + const isPast = isPastDate(startsAt, workhour); + const isDimmed = closed || isPast; + const { displayRate, rateText } = getPayRateText( + hourlyPay, + originalHourlyPay, + ); + + return ( + +
+
+ {name} + {isDimmed && ( +

+ {closed ? "마감 완료" : "지난 공고"} +

+ )} +
+ +
+
+

{name}

+

+ + {timeRange} +

+

+ + {address1} +

+
+ +
+

+ {hourlyPay.toLocaleString()}원 +

+ + {displayRate > 0 && ( + = 50 + ? "md:bg-red-40" + : displayRate >= 25 + ? "md:bg-red-30" + : "md:bg-red-20", + )} + > + {rateText} + + + + )} +
+
+
+ + ); +} diff --git a/src/utils/cn.ts b/src/utils/cn.ts new file mode 100644 index 0000000..0f46f37 --- /dev/null +++ b/src/utils/cn.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export const cn = (...inputs: ClassValue[]) => { + return twMerge(clsx(inputs)); +};