Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 4 additions & 5 deletions app/components/common/FilterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ const truncateLabel = (text: string, maxLength = 10) => {
export default function FilterButton({ label, isActive, className = "", ...props }: FilterButtonProps) {
return (
<button
className={`flex items-center gap-1 px-[10px] py-1 rounded-[60px] border transition-colors cursor-pointer ${
isActive
? "border-core-1 bg-core-1/10 text-core-1"
: "border-[#E6E6F3] bg-white text-text-gray2"
} ${className}`}
className={`flex items-center gap-1 px-2.5 py-1 h-7 rounded-[60px] border transition-colors cursor-pointer ${isActive
? "border-core-1 bg-core-1/10 text-core-1"
: "border-[#E6E6F3] bg-white text-text-gray2"
} ${className}`}
{...props}
>
{truncateLabel(label)}
Expand Down
85 changes: 85 additions & 0 deletions app/data/proposalTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export interface ProposalTag {
id: number;
name: string;
}

export interface ProposalTagGroup {
sort: string;
sortKorName: string;
tags: ProposalTag[];
}

export const PROPOSAL_TAGS: ProposalTagGroup[] = [
{
sort: "FORMAT",
sortKorName: "형식",
tags: [
{ id: 1, name: "인스타 스토리" },
{ id: 2, name: "인스타 포스트" },
{ id: 3, name: "인스타 릴스" },
{ id: 4, name: "기타" },
],
},
{
sort: "CATEGORY",
sortKorName: "종류",
tags: [
{ id: 5, name: "브이로그" },
{ id: 6, name: "리뷰" },
{ id: 7, name: "겟레디윗미" },
{ id: 8, name: "비포&애프터" },
{ id: 9, name: "스토리/썰" },
{ id: 10, name: "챌린지" },
{ id: 11, name: "기타" },
],
},
{
sort: "TONE",
sortKorName: "톤",
tags: [
{ id: 12, name: "전문적인" },
{ id: 13, name: "감성적인" },
{ id: 14, name: "유쾌/재밌는" },
{ id: 15, name: "트렌디한" },
{ id: 16, name: "일상적인" },
{ id: 17, name: "수다적인" },
{ id: 18, name: "기타" },
],
},
{
sort: "INVOLVEMENT",
sortKorName: "관여도",
tags: [
{ id: 19, name: "관여안함" },
{ id: 20, name: "가이드만 제공" },
{ id: 21, name: "대본 일부 제공" },
{ id: 22, name: "모든 연출 관여" },
{ id: 23, name: "기타" },
],
},
{
sort: "USAGE_RANGE",
sortKorName: "활용 범위",
tags: [
{ id: 24, name: "크리에이터 1차활용" },
{ id: 25, name: "브랜드 2차활용" },
{ id: 26, name: "기타" },
],
},
];

const findGroup = (sort: string) =>
PROPOSAL_TAGS.find((g) => g.sort === sort)!;

export const FORMAT_TAGS = findGroup("FORMAT").tags;
export const CATEGORY_TAGS = findGroup("CATEGORY").tags;
export const TONE_TAGS = findGroup("TONE").tags;
export const INVOLVEMENT_TAGS = findGroup("INVOLVEMENT").tags;
export const USAGE_RANGE_TAGS = findGroup("USAGE_RANGE").tags;

/** name → id 매핑 (전체) */
export const PROPOSAL_TAG_ID_BY_NAME: Record<string, number> =
PROPOSAL_TAGS.flatMap((g) => g.tags).reduce(
(acc, t) => ({ ...acc, [t.name]: t.id }),
{} as Record<string, number>,
);
1 change: 0 additions & 1 deletion app/routes/chat/components/CampaignListBottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export default function CampaignListBottomSheet({ isOpen, onClose, onSelect }: P
selectedValues={[]}
onSubmit={handleSubmit}
multiSelect={false}
hasCustomInput={false}
/>
);
}
66 changes: 22 additions & 44 deletions app/routes/chat/resuggest/resuggest-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ import {
type CampaignFormData,
} from "../../matching/suggest/create/schema";
import {
CONTENT_FILTER,
} from "../../../data/filter";
import { TAG_NAME_BY_ID } from "../../../data/tagNameById";
FORMAT_TAGS,
CATEGORY_TAGS,
TONE_TAGS,
INVOLVEMENT_TAGS,
USAGE_RANGE_TAGS,
PROPOSAL_TAG_ID_BY_NAME,
} from "../../../data/proposalTags";

export default function ReSuggestContent() {
const navigate = useNavigate();
Expand Down Expand Up @@ -73,20 +77,6 @@ export default function ReSuggestContent() {
mode: "onChange",
});

// 태그 이름으로 ID를 찾는 맵 생성
const ID_BY_TAG_NAME: Record<string, number> = Object.entries(TAG_NAME_BY_ID).reduce(
(acc, [id, name]) => ({ ...acc, [name]: Number(id) }),
{}
);

// 태그 매핑 보정
const getMappedId = (name: string) => {
if (name === "인스타 포스트" || name === "인스타 포스터") return 172;
if (name === "스토리&썰" || name === "스토리/썰") return 178;
if (name === "가이드만 제공" || name === "가이드 라인만 제공") return 187;
return ID_BY_TAG_NAME[name];
};

// 초기 값 채우기
useEffect(() => {
if (proposalData) {
Expand All @@ -95,27 +85,27 @@ export default function ReSuggestContent() {

if (proposalData.contentTags?.formats && proposalData.contentTags.formats.length > 0) {
const t = proposalData.contentTags.formats[0];
const id = t.id ?? getMappedId(t.name);
const id = t.id ?? PROPOSAL_TAG_ID_BY_NAME[t.name];
if (id) setValue("format", String(id));
}
if (proposalData.contentTags?.categories && proposalData.contentTags.categories.length > 0) {
const t = proposalData.contentTags.categories[0];
const id = t.id ?? getMappedId(t.name);
const id = t.id ?? PROPOSAL_TAG_ID_BY_NAME[t.name];
if (id) setValue("category", String(id));
}
if (proposalData.contentTags?.tones && proposalData.contentTags.tones.length > 0) {
const t = proposalData.contentTags.tones[0];
const id = t.id ?? getMappedId(t.name);
const id = t.id ?? PROPOSAL_TAG_ID_BY_NAME[t.name];
if (id) setValue("tone", String(id));
}
if (proposalData.contentTags?.involvements && proposalData.contentTags.involvements.length > 0) {
const t = proposalData.contentTags.involvements[0];
const id = t.id ?? getMappedId(t.name);
const id = t.id ?? PROPOSAL_TAG_ID_BY_NAME[t.name];
if (id) setValue("involvement", String(id));
}
if (proposalData.contentTags?.usageRanges && proposalData.contentTags.usageRanges.length > 0) {
const t = proposalData.contentTags.usageRanges[0];
const id = t.id ?? getMappedId(t.name);
const id = t.id ?? PROPOSAL_TAG_ID_BY_NAME[t.name];
if (id) setValue("usageScope", String(id));
}

Expand All @@ -140,35 +130,22 @@ export default function ReSuggestContent() {

const formValues = useWatch({ control, defaultValue: defaultCampaignFormValues });

const getOptions = (filterKeys: readonly string[]) => {
return filterKeys.map((name) => ({
value: String(getMappedId(name) || name),
label: name,
}));
};

const formatOptions = getOptions(CONTENT_FILTER.형식);
const categoryOptions = getOptions(CONTENT_FILTER.종류);
const toneOptions = getOptions(CONTENT_FILTER.톤);
const involvementOptions = getOptions(CONTENT_FILTER.관여도);
const usageScopeOptions = getOptions(CONTENT_FILTER["활용 범위"]);
const formatOptions = FORMAT_TAGS.map((t) => ({ value: String(t.id), label: t.name }));
const categoryOptions = CATEGORY_TAGS.map((t) => ({ value: String(t.id), label: t.name }));
const toneOptions = TONE_TAGS.map((t) => ({ value: String(t.id), label: t.name }));
const involvementOptions = INVOLVEMENT_TAGS.map((t) => ({ value: String(t.id), label: t.name }));
const usageScopeOptions = USAGE_RANGE_TAGS.map((t) => ({ value: String(t.id), label: t.name }));

const sponsorProductOptions = proposalData?.product
? [{ value: proposalData.product, label: proposalData.product }]
: (proposalData?.products ?? []).map((p) => ({ value: String(p.id), label: p.name }));
: (proposalData?.products ?? [])
.filter((p) => p.id && p.name)
.map((p) => ({ value: String(p.id), label: p.name }));

const findLabel = (options: { value: string; label: string }[], value?: string) => {
if (!value) return undefined;
const option = options.find((opt) => opt.value === value);
if (option) return option.label;

// Fallback to global tag mapping if value is a numeric ID
const numericId = Number(value);
if (!isNaN(numericId) && TAG_NAME_BY_ID[numericId]) {
return TAG_NAME_BY_ID[numericId];
}

return "";
return option?.label || "";
};

const onSubmit = () => {
Expand Down Expand Up @@ -466,6 +443,7 @@ export default function ReSuggestContent() {
selectedValues={formValues.sponsorProduct ? [formValues.sponsorProduct] : []}
onSubmit={(values) => setValue("sponsorProduct", values[0] || "", { shouldValidate: true })}
multiSelect={false}
hasCustomInput={true}
/>
<DatePickerBottomSheet
isOpen={isStartDateSheetOpen}
Expand Down
11 changes: 4 additions & 7 deletions app/routes/matching/brand/brand-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function BrandContent() {
// 필터 상태
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [filterOpenTab, setFilterOpenTab] = useState<"sort" | "filter">("sort");
const [sortOption, setSortOption] = useState("정렬 필터");
const [sortOption, setSortOption] = useState("매칭률 순");
const [selectedTags, setSelectedTags] = useState<string[]>([]);

// 검색 상태
Expand All @@ -45,7 +45,6 @@ export default function BrandContent() {
} = useInfiniteQuery({
queryKey: ["matching-brands", category, sortOption, selectedTags],
queryFn: async () => {
// 페이지네이션 없이 한 번만 호출
const response = await getMatchingBrands(sortBy, category, selectedTags.length > 0 ? selectedTags : undefined);
return response;
},
Expand Down Expand Up @@ -117,9 +116,6 @@ export default function BrandContent() {
setSelectedTags(tags);
};

const getSortButtonLabel = () => {
return sortOption;
};

const getFilterButtonLabel = () => {
if (selectedTags.length > 0) {
Expand Down Expand Up @@ -175,14 +171,15 @@ export default function BrandContent() {
<h2 className="text-title1 mb-4">브랜드 리스트</h2>
<div className="flex gap-2">
<FilterButton
label={getSortButtonLabel()}
isActive={sortOption !== "정렬 필터"}
label={sortOption}
isActive={true}
onClick={() => { setFilterOpenTab("sort"); setIsFilterOpen(true); }}
/>
<FilterButton
label={getFilterButtonLabel()}
isActive={selectedTags.length > 0}
onClick={() => { setFilterOpenTab("filter"); setIsFilterOpen(true); }}
className="bg-transparent border-core-1"
/>
</div>
</div>
Expand Down
11 changes: 5 additions & 6 deletions app/routes/matching/campaign/campaign-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function CampaignContent() {

// 필터 상태
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [sortOption, setSortOption] = useState("정렬 필터");
const [sortOption, setSortOption] = useState("매칭률 순");
const [selectedTags, setSelectedTags] = useState<string[]>([]);

// 검색 상태
Expand Down Expand Up @@ -131,9 +131,7 @@ export default function CampaignContent() {
setSelectedTags(tags);
};

const getSortButtonLabel = () => {
return sortOption;
};


const getFilterButtonLabel = () => {
if (selectedTags.length > 0) {
Expand Down Expand Up @@ -181,14 +179,15 @@ export default function CampaignContent() {
<h2 className="text-title1 mb-4">캠페인 리스트</h2>
<div className="flex gap-2">
<FilterButton
label={getSortButtonLabel()}
isActive={sortOption !== "정렬 필터"}
label={sortOption}
isActive={true}
onClick={() => setIsFilterOpen(true)}
/>
<FilterButton
label={getFilterButtonLabel()}
isActive={selectedTags.length > 0}
onClick={() => setIsFilterOpen(true)}
className="bg-transparent border-core-1"
/>
</div>
</div>
Expand Down
7 changes: 6 additions & 1 deletion app/routes/matching/components/ProposalModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import CheckCircleIcon from "../../../assets/icon/icon-check-circle.svg";
interface ProposalModalProps {
isOpen: boolean;
type: "confirm" | "success";
variant?: "apply" | "suggest";
onClose: () => void;
onConfirm?: () => void;
}

export default function ProposalModal({
isOpen,
type,
variant = "apply",
onClose,
onConfirm,
}: ProposalModalProps) {
const isConfirm = type === "confirm";
const isSuggest = variant === "suggest";

return (
<Modal
Expand All @@ -40,7 +43,9 @@ export default function ProposalModal({
<img src={CheckCircleIcon} alt="" className="w-16 h-16 mb-6" />

<h3 className="text-callout3 text-text-black text-center">
{isConfirm ? "지원하시겠습니까?" : "지원 완료"}
{isConfirm
? (isSuggest ? "캠페인을 제안하시겠습니까?" : "지원하시겠습니까?")
: (isSuggest ? "제안하기 완료" : "지원 완료")}
</h3>

{!isConfirm && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function SelectBottomSheet({
selectedValues,
onSubmit,
multiSelect = false,
hasCustomInput = true,
hasCustomInput = false,
}: SelectBottomSheetProps) {
const [selected, setSelected] = useState<string[]>(selectedValues);
const [customInput, setCustomInput] = useState("");
Expand Down Expand Up @@ -112,7 +112,7 @@ export default function SelectBottomSheet({
variant="primary"
size="lg"
onClick={handleSubmit}
className="text-title7 w-[327px] h-[44px] flex items-center justify-center gap-[10px]"
className="text-title7 w-[90%] h-[44px] flex items-center justify-center gap-[10px] rounded-[12px]"
>
선택 완료
</Button>
Expand Down
Loading
Loading