[fix] 디렉토리 구조 변경 및 파일명칭 수정
parent
c29541a365
commit
45f6a9f6ab
31
README.md
31
README.md
|
|
@ -10,25 +10,36 @@
|
|||
|
||||
## 디렉토리 구조
|
||||
|
||||
디렉토리 구조는 다음을 따를 예정입니다.
|
||||
```bash
|
||||
src/
|
||||
├── app/ # 애플리케이션 진입점 및 전역 설정 (Router, Providers, 글로벌 스타일)
|
||||
├── assets/ # 정적 파일 (이미지, 폰트, 로티 애니메이션 등)
|
||||
├── components/ # 도메인에 종속되지 않는 공통 UI 컴포넌트 (버튼, 모달, 디자인 시스템)
|
||||
│ └── providers/ # React Context Provider 모음 (QueryProvider 등)
|
||||
├── assets/ # 정적 파일 (이미지, 폰트, SVG 아이콘 등)
|
||||
├── components/ # 도메인에 종속되지 않는 공통 UI 컴포넌트 (버튼, 카드, 디자인 시스템)
|
||||
├── features/ # 핵심 비즈니스 로직 및 도메인 영역 (이 구조의 핵심)
|
||||
│ ├── auth/ # 특정 도메인 (예: 인증)
|
||||
│ │ ├── api/ # 해당 도메인 전용 API 통신 함수
|
||||
│ ├── home/ # 홈(랜딩) 도메인
|
||||
│ │ ├── content/ # 해당 도메인 전용 UI 텍스트·카피 (정적 콘텐츠)
|
||||
│ │ ├── hooks/ # 해당 도메인 전용 커스텀 훅
|
||||
│ │ ├── store/ # 해당 도메인 전용 상태 (Zustand 등)
|
||||
│ │ └── ui/ # 해당 도메인 전용 UI 컴포넌트
|
||||
│ ├── report/ # 리포트 도메인
|
||||
│ │ ├── config/ # 섹션 ID·레이블 등 UI 설정값
|
||||
│ │ ├── hooks/ # 해당 도메인 전용 커스텀 훅
|
||||
│ │ ├── mocks/ # API 연동 전 임시 목업 데이터
|
||||
│ │ ├── types/ # 해당 도메인 전용 타입 정의
|
||||
│ │ └── ui/ # 해당 도메인 전용 UI 컴포넌트
|
||||
├── hooks/ # 전역에서 사용하는 공통 훅 (useClickOutside 등)
|
||||
├── layouts/ # 페이지 레이아웃 (GNB, Sidebar, 풋터 등)
|
||||
├── pages/ # 라우팅과 1:1 매칭되는 페이지 진입점 (여기서는 features의 컴포넌트만 조립)
|
||||
│ └── plan/ # 마케팅 플랜 도메인 (report와 동일한 구조)
|
||||
│ ├── config/
|
||||
│ ├── hooks/
|
||||
│ ├── mocks/
|
||||
│ ├── types/
|
||||
│ └── ui/
|
||||
├── hooks/ # 전역에서 사용하는 공통 훅 (useInView 등)
|
||||
├── layouts/ # 페이지 레이아웃 (GNB, SubNav, Footer 등)
|
||||
├── pages/ # 라우팅과 1:1 매칭되는 페이지 진입점 (features의 컴포넌트만 조립)
|
||||
├── services/ # 공통 API 클라이언트 설정 (Axios 인스턴스, 인터셉터 등)
|
||||
├── store/ # 전역 상태 관리 (사용자 세션, 테마 등)
|
||||
└── utils/ # 공통 유틸리티 함수 (날짜 포맷팅, 정규식 등)
|
||||
├── types/ # 여러 도메인에서 공유하는 공통 타입 정의
|
||||
└── utils/ # 공통 유틸리티 함수 (숫자 포맷팅, URL 처리 등)
|
||||
```
|
||||
|
||||
## 시작하기
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { CSSProperties } from "react";
|
||||
import EyeIcon from "@/assets/icons/eye.svg?react";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
|
||||
export type TopVideoCardProps = {
|
||||
title: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
export { ComparisonRow, type ComparisonRowProps } from "@/components/compare/ComparisonRow";
|
||||
export { BrandConsistencyMap, type BrandConsistencyMapProps } from "@/components/brand/BrandConsistencyMap";
|
||||
export { OtherChannelRow, type OtherChannelRowProps } from "@/components/channel/OtherChannelRow";
|
||||
export { SeverityBadge, type SeverityBadgeProps } from "@/components/badge/SeverityBadge";
|
||||
export { ChannelScoreCard, type ChannelScoreCardProps } from "@/components/card/ChannelScoreCard";
|
||||
export { InfoStatCard, type InfoStatCardProps } from "@/components/card/InfoStatCard";
|
||||
|
|
@ -8,7 +6,6 @@ export { MetricCard, type MetricCardProps } from "@/components/card/MetricCard";
|
|||
export { PixelInstallCard, type PixelInstallCardProps } from "@/components/card/PixelInstallCard";
|
||||
export { TopVideoCard, type TopVideoCardProps } from "@/components/card/TopVideoCard";
|
||||
export { TagChipList, type TagChipListProps } from "@/components/chip/TagChipList";
|
||||
export { DiagnosisRow, type DiagnosisRowProps } from "@/components/diagnosis/DiagnosisRow";
|
||||
export { ConsolidationCallout, type ConsolidationCalloutProps } from "@/components/panel/ConsolidationCallout";
|
||||
export { HighlightPanel, type HighlightPanelProps } from "@/components/panel/HighlightPanel";
|
||||
export { ScoreRing, type ScoreRingProps } from "@/components/rating/ScoreRing";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import StarIcon from "@/assets/icons/star.svg?react";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
|
||||
export type StarRatingDisplayProps = {
|
||||
rating: number;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
CTA_FOOTNOTE,
|
||||
CTA_HEADLINE,
|
||||
CTA_URL_PLACEHOLDER,
|
||||
} from "@/features/home/constants/cta_contents";
|
||||
} from "@/features/home/content/cta";
|
||||
import { useAnalyze } from "@/features/home/hooks/useAnalyze";
|
||||
import { useInView } from "@/hooks/useInView";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
HERO_LEAD_EN,
|
||||
HERO_LEAD_KO,
|
||||
HERO_URL_PLACEHOLDER,
|
||||
} from "@/features/home/constants/hero_contents";
|
||||
} from "@/features/home/content/hero";
|
||||
import { useAnalyze } from "@/features/home/hooks/useAnalyze";
|
||||
|
||||
export function HeroSection() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { PROBLEM_CARDS, PROBLEM_CARD_STAGGER } from "@/features/home/constants/problem_contents";
|
||||
import { PROBLEM_CARDS, PROBLEM_CARD_STAGGER } from "@/features/home/content/problem";
|
||||
import { useInView } from "@/hooks/useInView";
|
||||
|
||||
export function ProblemSection() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SOLUTION_CARDS } from "@/features/home/constants/solution_contents";
|
||||
import { SOLUTION_CARDS } from "@/features/home/content/solution";
|
||||
import { useInView } from "@/hooks/useInView";
|
||||
|
||||
export function SolutionSection() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CORE_MODULES, MODULE_CARD_STAGGER } from "@/features/home/constants/modules_contents";
|
||||
import { CORE_MODULES, MODULE_CARD_STAGGER } from "@/features/home/content/modules";
|
||||
import { useInView } from "@/hooks/useInView";
|
||||
import { CoreModuleCard } from "./system/CoreModuleCard";
|
||||
import { CoreModulesCenterHeading } from "./system/CoreModulesCenterHeading";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import CheckCircleIcon from "@/assets/home/check-circle.svg?react";
|
||||
import { USE_CASE_CARDS } from "@/features/home/constants/use_cases_contents";
|
||||
import { USE_CASE_CARDS } from "@/features/home/content/useCases";
|
||||
import { useInView } from "@/hooks/useInView";
|
||||
|
||||
export function UseCaseSection() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AGDP_NODES } from "@/features/home/constants/process_contents";
|
||||
import { AGDP_NODES } from "@/features/home/content/process";
|
||||
import { AgdpOrbitNode } from "./AgdpOrbitNode";
|
||||
import { AgdpRewardPathLabel } from "./AgdpRewardPathLabel";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { AgdpNodeDef } from "@/features/home/constants/process_contents";
|
||||
import { AGDP_SLOT_WRAPPER_CLASS } from "@/features/home/constants/process_contents";
|
||||
import type { AgdpNodeDef } from "@/features/home/content/process";
|
||||
import { AGDP_SLOT_WRAPPER_CLASS } from "@/features/home/content/process";
|
||||
|
||||
type Props = { node: AgdpNodeDef };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { CoreModule } from "@/features/home/constants/modules_contents";
|
||||
import type { CoreModule } from "@/features/home/content/modules";
|
||||
|
||||
type Props = {
|
||||
mod: CoreModule;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useMemo } from "react";
|
||||
import { MOCK_PLAN } from "@/features/plan/constants/mock_plan";
|
||||
import { MOCK_PLAN } from "@/features/plan/mocks/plan";
|
||||
import type { MarketingPlan } from "@/features/plan/types/marketingPlan";
|
||||
|
||||
type UseMarketingPlanResult = {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMainSubNav } from "@/layouts/MainSubNavLayout";
|
||||
import type { SubNavItem } from "@/layouts/SubNav";
|
||||
import { PLAN_SECTIONS } from "@/features/plan/constants/plan_sections";
|
||||
import { PLAN_SECTIONS } from "@/features/plan/config/planSections";
|
||||
|
||||
export function usePlanSubNav() {
|
||||
const { setSubNav } = useMainSubNav();
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import { useCurrentMarketingPlan } from "@/features/plan/hooks/useCurrentMarketingPlan";
|
||||
import { usePlanSubNav } from "@/features/plan/hooks/usePlanSubNav";
|
||||
import { PlanAssetCollectionSection } from "@/features/plan/ui/PlanAssetCollectionSection";
|
||||
import { PlanBrandingGuideSection } from "@/features/plan/ui/PlanBrandingGuideSection";
|
||||
import { PlanChannelStrategySection } from "@/features/plan/ui/PlanChannelStrategySection";
|
||||
import { PlanContentCalendarSection } from "@/features/plan/ui/PlanContentCalendarSection";
|
||||
import { PlanContentStrategySection } from "@/features/plan/ui/PlanContentStrategySection";
|
||||
import { PlanCtaSection } from "@/features/plan/ui/PlanCtaSection";
|
||||
import { PlanHeaderSection } from "@/features/plan/ui/PlanHeaderSection";
|
||||
import { PlanMyAssetUploadSection } from "@/features/plan/ui/PlanMyAssetUploadSection";
|
||||
|
||||
export function MarketingPlanPage() {
|
||||
const { data, error } = useCurrentMarketingPlan();
|
||||
|
||||
usePlanSubNav();
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center px-6">
|
||||
<div className="text-center max-w-md">
|
||||
<p className="title-16 text-[var(--color-status-critical-text)] mb-2 break-keep">
|
||||
오류가 발생했습니다
|
||||
</p>
|
||||
<p className="body-14 text-neutral-60 break-keep">
|
||||
{error ?? "마케팅 플랜을 찾을 수 없습니다."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-plan-content data-plan-id={data.id}>
|
||||
<PlanHeaderSection />
|
||||
<PlanBrandingGuideSection />
|
||||
<PlanChannelStrategySection />
|
||||
<PlanContentStrategySection />
|
||||
<PlanContentCalendarSection />
|
||||
<PlanAssetCollectionSection />
|
||||
<PlanMyAssetUploadSection />
|
||||
<PlanCtaSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { useCurrentMarketingPlan } from "@/features/plan/hooks/useCurrentMarketi
|
|||
import { PlanHeaderDaysBadge } from "@/features/plan/ui/header/PlanHeaderDaysBadge";
|
||||
import { PlanHeaderHeroBlobs } from "@/features/plan/ui/header/PlanHeaderHeroBlobs";
|
||||
import { PlanHeaderHeroColumn } from "@/features/plan/ui/header/PlanHeaderHeroColumn";
|
||||
import { PLAN_HEADER_BG_CLASS } from "@/features/plan/ui/header/planHeaderSectionStyles";
|
||||
import { PLAN_HEADER_BG_CLASS } from "@/features/plan/ui/header/planHeaderSectionClasses";
|
||||
|
||||
export function PlanHeaderSection() {
|
||||
const { data, error } = useCurrentMarketingPlan();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
assetTypeBadgeClass,
|
||||
assetTypeDisplayLabel,
|
||||
formatYoutubeViews,
|
||||
} from "@/features/plan/ui/assetCollection/assetCollectionBadgeClass";
|
||||
} from "@/features/plan/ui/assetCollection/assetCollectionBadgeClasses";
|
||||
|
||||
type AssetCollectionPanelProps = {
|
||||
data: AssetCollectionData;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { BrandingChannelIcon } from "@/features/plan/ui/branding/BrandingChannel
|
|||
import {
|
||||
brandingChannelStatusBadgeClass,
|
||||
brandingChannelStatusLabel,
|
||||
} from "@/features/plan/ui/branding/brandingChannelStatusClass";
|
||||
} from "@/features/plan/ui/branding/brandingChannelStatusClasses";
|
||||
|
||||
type BrandingChannelRulesTabProps = {
|
||||
channels: BrandGuide["channelBranding"];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState } from "react";
|
|||
import { BrandConsistencyMap } from "@/components/brand/BrandConsistencyMap";
|
||||
import { SegmentTabButton } from "@/features/plan/ui/SegmentTabButton";
|
||||
import type { BrandGuide } from "@/features/plan/types/marketingPlan";
|
||||
import { BRANDING_GUIDE_TAB_ITEMS, type BrandingGuideTabKey } from "@/features/plan/ui/branding/brandingTabItems";
|
||||
import { BRANDING_GUIDE_TAB_ITEMS, type BrandingGuideTabKey } from "@/features/plan/ui/branding/brandingTabs";
|
||||
import { BrandingChannelRulesTab } from "@/features/plan/ui/branding/BrandingChannelRulesTab";
|
||||
import { BrandingToneVoiceTab } from "@/features/plan/ui/branding/BrandingToneVoiceTab";
|
||||
import { BrandingVisualIdentityTab } from "@/features/plan/ui/branding/BrandingVisualIdentityTab";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Pill } from "@/components/atoms/Pill";
|
|||
import { Surface } from "@/components/atoms/Surface";
|
||||
import type { ChannelStrategyCard } from "@/features/plan/types/marketingPlan";
|
||||
import { BrandingChannelIcon } from "@/features/plan/ui/branding/BrandingChannelIcon";
|
||||
import { channelStrategyPriorityPillClass } from "@/features/plan/ui/channelStrategy/channelStrategyPillClass";
|
||||
import { channelStrategyPriorityPillClass } from "@/features/plan/ui/channelStrategy/channelStrategyPillClasses";
|
||||
|
||||
type ChannelStrategyGridProps = {
|
||||
channels: ChannelStrategyCard[];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
CALENDAR_CONTENT_TYPE_LABELS,
|
||||
CALENDAR_DAY_HEADERS,
|
||||
calendarContentTypeVisual,
|
||||
} from "@/features/plan/ui/contentCalendar/calendarContentTypeVisual";
|
||||
} from "@/features/plan/ui/contentCalendar/calendarContentTypeClasses";
|
||||
import { ContentCalendarTypeIcon } from "@/features/plan/ui/contentCalendar/ContentCalendarTypeIcon";
|
||||
|
||||
type ContentCalendarPanelProps = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type { ContentStrategyData } from "@/features/plan/types/marketingPlan";
|
|||
import {
|
||||
CONTENT_STRATEGY_TAB_ITEMS,
|
||||
type ContentStrategyTabKey,
|
||||
} from "@/features/plan/ui/contentStrategy/contentStrategyTabItems";
|
||||
} from "@/features/plan/ui/contentStrategy/contentStrategyTabs";
|
||||
import { ContentStrategyPillarsTab } from "@/features/plan/ui/contentStrategy/ContentStrategyPillarsTab";
|
||||
import { ContentStrategyRepurposingTab } from "@/features/plan/ui/contentStrategy/ContentStrategyRepurposingTab";
|
||||
import { ContentStrategyTypesTab } from "@/features/plan/ui/contentStrategy/ContentStrategyTypesTab";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Pill } from "@/components/atoms/Pill";
|
||||
import type { ContentTypeRow } from "@/features/plan/types/marketingPlan";
|
||||
import { contentStrategyChannelBadgeClass } from "@/features/plan/ui/contentStrategy/contentStrategyChannelBadgeClass";
|
||||
import { contentStrategyChannelBadgeClass } from "@/features/plan/ui/contentStrategy/contentStrategyChannelBadgeClasses";
|
||||
|
||||
type ContentStrategyTypesTabProps = {
|
||||
rows: ContentTypeRow[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import CalendarIcon from "@/assets/report/calendar.svg?react";
|
||||
import GlobeIcon from "@/assets/report/globe.svg?react";
|
||||
import { PLAN_HEADER_META_CHIP_CLASS } from "@/features/plan/ui/header/planHeaderSectionStyles";
|
||||
import { PLAN_HEADER_META_CHIP_CLASS } from "@/features/plan/ui/header/planHeaderSectionClasses";
|
||||
|
||||
type PlanHeaderMetaChipsProps = {
|
||||
date: string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMainSubNav } from "@/layouts/MainSubNavLayout";
|
||||
import type { SubNavItem } from "@/layouts/SubNav";
|
||||
import { REPORT_SECTIONS } from "@/features/report/constants/report_sections";
|
||||
import { REPORT_SECTIONS } from "@/features/report/config/reportSections";
|
||||
|
||||
export function useReportSubNav() {
|
||||
const { setSubNav } = useMainSubNav();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { OtherChannelsReport } from "@/types/otherChannels";
|
||||
import type { OtherChannelsReport } from "@/features/report/types/otherChannels";
|
||||
|
||||
/** DEMO `mockReport` 기타 채널 + 웹사이트 진단 */
|
||||
export const MOCK_OTHER_CHANNELS_REPORT: OtherChannelsReport = {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_CHANNEL_SCORES } from "@/features/report/constants/mock_channel_scores";
|
||||
import { MOCK_CHANNEL_SCORES } from "@/features/report/mocks/channelScores";
|
||||
import type { ChannelScore } from "@/features/report/types/channelScore";
|
||||
import { ChannelScoreGrid } from "@/features/report/ui/channels/ChannelScoreGrid";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_CLINIC_SNAPSHOT } from "@/features/report/constants/mock_clinic_snapshot";
|
||||
import { MOCK_CLINIC_SNAPSHOT } from "@/features/report/mocks/clinicSnapshot";
|
||||
import type { ClinicSnapshot } from "@/features/report/types/clinicSnapshot";
|
||||
import { ClinicCertificationsBlock } from "@/features/report/ui/clinic/ClinicCertificationsBlock";
|
||||
import { ClinicInfoStatGrid } from "@/features/report/ui/clinic/ClinicInfoStatGrid";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_PROBLEM_DIAGNOSIS } from "@/features/report/constants/mock_problem_diagnosis";
|
||||
import { MOCK_PROBLEM_DIAGNOSIS } from "@/features/report/mocks/problemDiagnosis";
|
||||
import type { DiagnosisItem } from "@/features/report/types/diagnosis";
|
||||
import { ProblemDiagnosisCard } from "@/features/report/ui/diagnosis/ProblemDiagnosisCard";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import GlobeIcon from "@/assets/report/globe.svg?react";
|
||||
import { BrandConsistencyMap } from "@/components/brand/BrandConsistencyMap";
|
||||
import { DiagnosisRow } from "@/components/diagnosis/DiagnosisRow";
|
||||
import { DiagnosisRow } from "@/features/report/ui/diagnosis/DiagnosisRow";
|
||||
import { ConsolidationCallout } from "@/components/panel/ConsolidationCallout";
|
||||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_FACEBOOK_AUDIT } from "@/features/report/constants/mock_facebook_audit";
|
||||
import { MOCK_FACEBOOK_AUDIT } from "@/features/report/mocks/facebookAudit";
|
||||
import type { FacebookAudit } from "@/features/report/types/facebookAudit";
|
||||
import { FacebookPageCard } from "@/features/report/ui/facebook/FacebookPageCard";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { DiagnosisRow } from "@/components/diagnosis/DiagnosisRow";
|
||||
import { DiagnosisRow } from "@/features/report/ui/diagnosis/DiagnosisRow";
|
||||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_INSTAGRAM_AUDIT } from "@/features/report/constants/mock_instagram_audit";
|
||||
import { MOCK_INSTAGRAM_AUDIT } from "@/features/report/mocks/instagramAudit";
|
||||
import type { InstagramAudit } from "@/features/report/types/instagramAudit";
|
||||
import { InstagramAccountCard } from "@/features/report/ui/instagram/InstagramAccountCard";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_KPI_METRICS } from "@/features/report/constants/mock_kpi";
|
||||
import { MOCK_KPI_METRICS } from "@/features/report/mocks/kpi";
|
||||
import type { KpiMetric } from "@/features/report/types/kpiDashboard";
|
||||
import { KpiMetricsTable } from "@/features/report/ui/kpi/KpiMetricsTable";
|
||||
import { KpiTransformationCtaCard } from "@/features/report/ui/kpi/KpiTransformationCtaCard";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_OTHER_CHANNELS_REPORT } from "@/features/report/constants/mock_other_channels";
|
||||
import type { OtherChannelsReport } from "@/types/otherChannels";
|
||||
import { MOCK_OTHER_CHANNELS_REPORT } from "@/features/report/mocks/otherChannels";
|
||||
import type { OtherChannelsReport } from "@/features/report/types/otherChannels";
|
||||
import { OtherChannelsList } from "@/features/report/ui/otherChannels/OtherChannelsList";
|
||||
import { WebsiteTechAuditBlock } from "@/features/report/ui/otherChannels/WebsiteTechAuditBlock";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { MOCK_REPORT_OVERVIEW } from "@/features/report/constants/mock_report_overview";
|
||||
import { MOCK_REPORT_OVERVIEW } from "@/features/report/mocks/reportOverview";
|
||||
import type { ReportOverviewData } from "@/features/report/types/reportOverview";
|
||||
import { OverviewHeroBlobs } from "@/features/report/ui/overview/OverviewHeroBlobs";
|
||||
import { OverviewHeroColumn } from "@/features/report/ui/overview/OverviewHeroColumn";
|
||||
import { OverviewScorePanel } from "@/features/report/ui/overview/OverviewScorePanel";
|
||||
import { OVERVIEW_SECTION_BG_CLASS } from "@/features/report/ui/overview/overviewSectionStyles";
|
||||
import { OVERVIEW_SECTION_BG_CLASS } from "@/features/report/ui/overview/overviewSectionClasses";
|
||||
|
||||
type ReportOverviewSectionProps = {
|
||||
data?: ReportOverviewData;
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
import { useParams } from "react-router-dom";
|
||||
import { useReportSubNav } from "@/features/report/hooks/useReportSubNav";
|
||||
import { ReportChannelsSection } from "@/features/report/ui/ReportChannelsSection";
|
||||
import { ReportClinicSection } from "@/features/report/ui/ReportClinicSection";
|
||||
import { ReportDiagnosisSection } from "@/features/report/ui/ReportDiagnosisSection";
|
||||
import { ReportFacebookSection } from "@/features/report/ui/ReportFacebookSection";
|
||||
import { ReportInstagramSection } from "@/features/report/ui/ReportInstagramSection";
|
||||
import { ReportKpiSection } from "@/features/report/ui/ReportKpiSection";
|
||||
import { ReportOtherChannelsSection } from "@/features/report/ui/ReportOtherChannelsSection";
|
||||
import { ReportOverviewSection } from "@/features/report/ui/ReportOverviewSection";
|
||||
import { ReportRoadmapSection } from "@/features/report/ui/ReportRoadmapSection";
|
||||
import { ReportTransformationSection } from "@/features/report/ui/ReportTransformationSection";
|
||||
import { ReportYouTubeSection } from "@/features/report/ui/ReportYouTubeSection";
|
||||
|
||||
export function ReportPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
useReportSubNav();
|
||||
|
||||
return (
|
||||
<div data-report-content>
|
||||
<p className="body-14 text-neutral-60 px-6 max-w-7xl mx-auto py-2">
|
||||
Report ID: <span className="text-navy-900 font-medium">{id}</span>
|
||||
</p>
|
||||
|
||||
<div className="divide-y divide-neutral-20">
|
||||
<ReportOverviewSection />
|
||||
<ReportClinicSection />
|
||||
<ReportChannelsSection />
|
||||
<ReportYouTubeSection />
|
||||
<ReportInstagramSection />
|
||||
<ReportFacebookSection />
|
||||
<ReportOtherChannelsSection />
|
||||
<ReportDiagnosisSection />
|
||||
<ReportTransformationSection />
|
||||
<ReportRoadmapSection />
|
||||
<ReportKpiSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_ROADMAP } from "@/features/report/constants/mock_roadmap";
|
||||
import { MOCK_ROADMAP } from "@/features/report/mocks/roadmap";
|
||||
import type { RoadmapMonth } from "@/features/report/types/roadmap";
|
||||
import { RoadmapMonthsGrid } from "@/features/report/ui/roadmap/RoadmapMonthsGrid";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_TRANSFORMATION } from "@/features/report/constants/mock_transformation";
|
||||
import { MOCK_TRANSFORMATION } from "@/features/report/mocks/transformation";
|
||||
import type { TransformationProposal } from "@/features/report/types/transformationProposal";
|
||||
import { TransformationTabbedView } from "@/features/report/ui/transformation/TransformationTabbedView";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { TagChipList } from "@/components/chip/TagChipList";
|
||||
import { DiagnosisRow } from "@/components/diagnosis/DiagnosisRow";
|
||||
import { DiagnosisRow } from "@/features/report/ui/diagnosis/DiagnosisRow";
|
||||
import { PageSection } from "@/components/section/PageSection";
|
||||
import { MOCK_YOUTUBE_AUDIT } from "@/features/report/constants/mock_youtube_audit";
|
||||
import { MOCK_YOUTUBE_AUDIT } from "@/features/report/mocks/youtubeAudit";
|
||||
import type { YouTubeAudit } from "@/features/report/types/youtubeAudit";
|
||||
import { YouTubeChannelInfoCard } from "@/features/report/ui/youtube/YouTubeChannelInfoCard";
|
||||
import { YouTubeMetricsGrid } from "@/features/report/ui/youtube/YouTubeMetricsGrid";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import CalendarIcon from "@/assets/report/calendar.svg?react";
|
|||
import GlobeIcon from "@/assets/report/globe.svg?react";
|
||||
import MapPinIcon from "@/assets/report/map-pin.svg?react";
|
||||
import type { ClinicSnapshot } from "@/features/report/types/clinicSnapshot";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
|
||||
export type ClinicStatRow = {
|
||||
label: string;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import AlertCircleIcon from "@/assets/icons/alert-circle.svg?react";
|
||||
import type { DiagnosisItem } from "@/features/report/types/diagnosis";
|
||||
import { problemDiagnosisSeverityDotClass } from "@/features/report/ui/diagnosis/severityDotClass";
|
||||
import { problemDiagnosisSeverityDotClass } from "@/features/report/ui/diagnosis/severityDotClasses";
|
||||
|
||||
export type ProblemDiagnosisCardProps = {
|
||||
item: DiagnosisItem;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import ImageIcon from "@/assets/report/image.svg?react";
|
|||
import Link2Icon from "@/assets/report/link-2.svg?react";
|
||||
import MessageCircleIcon from "@/assets/report/message-circle.svg?react";
|
||||
import type { FacebookPage } from "@/features/report/types/facebookAudit";
|
||||
import { facebookLangBadgeClass } from "@/features/report/ui/facebook/langBadgeClass";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
import { facebookLangBadgeClass } from "@/features/report/ui/facebook/langBadgeClasses";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
import { safeUrl } from "@/utils/safeUrl";
|
||||
|
||||
export type FacebookPageCardProps = {
|
||||
page: FacebookPage;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import ChannelInstagramIcon from "@/assets/icons/channel-instagram.svg?react";
|
|||
import ExternalLinkIcon from "@/assets/icons/external-link.svg?react";
|
||||
import { TagChipList } from "@/components/chip/TagChipList";
|
||||
import type { InstagramAccount } from "@/features/report/types/instagramAudit";
|
||||
import { instagramLangBadgeClass } from "@/features/report/ui/instagram/langBadgeClass";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
import { instagramLangBadgeClass } from "@/features/report/ui/instagram/langBadgeClasses";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
import { safeUrl } from "@/utils/safeUrl";
|
||||
|
||||
export type InstagramAccountCardProps = {
|
||||
account: InstagramAccount;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import ExternalLinkIcon from "@/assets/icons/external-link.svg?react";
|
|||
import CheckCircleIcon from "@/assets/report/check-circle.svg?react";
|
||||
import HelpCircleIcon from "@/assets/report/help-circle.svg?react";
|
||||
import XCircleIcon from "@/assets/report/x-circle.svg?react";
|
||||
import type { OtherChannelStatus } from "@/types/otherChannels";
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
import type { OtherChannelStatus } from "@/features/report/types/otherChannels";
|
||||
import { safeUrl } from "@/utils/safeUrl";
|
||||
|
||||
export type OtherChannelRowProps = {
|
||||
name: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { OtherChannelRow } from "@/components/channel/OtherChannelRow";
|
||||
import type { OtherChannel } from "@/types/otherChannels";
|
||||
import { OtherChannelRow } from "@/features/report/ui/otherChannels/OtherChannelRow";
|
||||
import type { OtherChannel } from "@/features/report/types/otherChannels";
|
||||
|
||||
export type OtherChannelsListProps = {
|
||||
channels: OtherChannel[];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import AlertCircleIcon from "@/assets/icons/alert-circle.svg?react";
|
|||
import CheckCircleIcon from "@/assets/report/check-circle.svg?react";
|
||||
import GlobeIcon from "@/assets/report/globe.svg?react";
|
||||
import { PixelInstallCard } from "@/components/card/PixelInstallCard";
|
||||
import type { WebsiteAudit } from "@/types/otherChannels";
|
||||
import type { WebsiteAudit } from "@/features/report/types/otherChannels";
|
||||
|
||||
export type WebsiteTechAuditBlockProps = {
|
||||
website: WebsiteAudit;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import CalendarIcon from "@/assets/report/calendar.svg?react";
|
||||
import GlobeIcon from "@/assets/report/globe.svg?react";
|
||||
import MapPinIcon from "@/assets/report/map-pin.svg?react";
|
||||
import { OVERVIEW_META_CHIP_CLASS } from "@/features/report/ui/overview/overviewSectionStyles";
|
||||
import { OVERVIEW_META_CHIP_CLASS } from "@/features/report/ui/overview/overviewSectionClasses";
|
||||
|
||||
export type OverviewMetaChipsProps = {
|
||||
date: string;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { NewChannelProposal } from "@/features/report/types/transformationProposal";
|
||||
import { newChannelPriorityClass } from "@/features/report/ui/transformation/newChannelPriorityClass";
|
||||
import { newChannelPriorityClass } from "@/features/report/ui/transformation/newChannelPriorityClasses";
|
||||
|
||||
export type NewChannelProposalsTableProps = {
|
||||
rows: NewChannelProposal[];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import { ComparisonRow } from "@/components/compare/ComparisonRow";
|
||||
import { ComparisonRow } from "@/features/report/ui/transformation/ComparisonRow";
|
||||
import type { TransformationProposal } from "@/features/report/types/transformationProposal";
|
||||
import { NewChannelProposalsTable } from "@/features/report/ui/transformation/NewChannelProposalsTable";
|
||||
import { PlatformStrategyCard } from "@/features/report/ui/transformation/PlatformStrategyCard";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import ChannelYoutubeIcon from "@/assets/icons/channel-youtube.svg?react";
|
||||
import ExternalLinkIcon from "@/assets/icons/external-link.svg?react";
|
||||
import type { YouTubeAudit } from "@/features/report/types/youtubeAudit";
|
||||
import { safeUrl } from "@/lib/safeUrl";
|
||||
import { safeUrl } from "@/utils/safeUrl";
|
||||
|
||||
export type YouTubeChannelInfoCardProps = {
|
||||
data: YouTubeAudit;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import VideoIcon from "@/assets/icons/video.svg?react";
|
|||
import UsersIcon from "@/assets/icons/users.svg?react";
|
||||
import { MetricCard } from "@/components/card/MetricCard";
|
||||
import type { YouTubeAudit } from "@/features/report/types/youtubeAudit";
|
||||
import { formatCompactNumber } from "@/lib/formatNumber";
|
||||
import { formatCompactNumber } from "@/utils/formatNumber";
|
||||
|
||||
export type YouTubeMetricsGridProps = {
|
||||
data: YouTubeAudit;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export function PageNavigator() {
|
|||
{/* 이전 페이지 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => prev && navigate(prev.navigatePath)}
|
||||
onClick={() => { if (prev) { navigate(prev.navigatePath); window.scrollTo({ top: 0, behavior: "smooth" }); } }}
|
||||
disabled={!prev}
|
||||
aria-label={prev ? `이전: ${prev.label}` : "이전 페이지 없음"}
|
||||
className="body-14-medium flex items-center gap-2 px-3 py-2 rounded-full text-neutral-70 transition-all hover:bg-neutral-20 hover:text-navy-900 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
||||
|
|
@ -83,7 +83,7 @@ export function PageNavigator() {
|
|||
<button
|
||||
key={page.id}
|
||||
type="button"
|
||||
onClick={() => navigate(page.navigatePath)}
|
||||
onClick={() => { navigate(page.navigatePath); window.scrollTo({ top: 0, behavior: "smooth" }); }}
|
||||
title={page.label}
|
||||
aria-label={page.label}
|
||||
className={`rounded-full transition-all cursor-pointer ${
|
||||
|
|
@ -98,7 +98,7 @@ export function PageNavigator() {
|
|||
{/* 다음 페이지 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => next && navigate(next.navigatePath)}
|
||||
onClick={() => { if (next) { navigate(next.navigatePath); window.scrollTo({ top: 0, behavior: "smooth" }); } }}
|
||||
disabled={!next}
|
||||
aria-label={next ? `다음: ${next.label}` : "다음 페이지 없음"}
|
||||
className="body-14-medium flex items-center gap-2 px-3 py-2 rounded-full text-neutral-70 transition-all hover:bg-neutral-20 hover:text-navy-900 disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,44 @@
|
|||
import { MarketingPlanPage } from "@/features/plan/ui/MarketingPlanPage";
|
||||
import { useCurrentMarketingPlan } from "@/features/plan/hooks/useCurrentMarketingPlan";
|
||||
import { usePlanSubNav } from "@/features/plan/hooks/usePlanSubNav";
|
||||
import { PlanHeaderSection } from "@/features/plan/ui/PlanHeaderSection";
|
||||
import { PlanBrandingGuideSection } from "@/features/plan/ui/PlanBrandingGuideSection";
|
||||
import { PlanChannelStrategySection } from "@/features/plan/ui/PlanChannelStrategySection";
|
||||
import { PlanContentStrategySection } from "@/features/plan/ui/PlanContentStrategySection";
|
||||
import { PlanContentCalendarSection } from "@/features/plan/ui/PlanContentCalendarSection";
|
||||
import { PlanAssetCollectionSection } from "@/features/plan/ui/PlanAssetCollectionSection";
|
||||
import { PlanMyAssetUploadSection } from "@/features/plan/ui/PlanMyAssetUploadSection";
|
||||
import { PlanCtaSection } from "@/features/plan/ui/PlanCtaSection";
|
||||
|
||||
export function PlanPage() {
|
||||
return <MarketingPlanPage />;
|
||||
const { data, error } = useCurrentMarketingPlan();
|
||||
|
||||
usePlanSubNav();
|
||||
|
||||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-[50vh] flex items-center justify-center px-6">
|
||||
<div className="text-center max-w-md">
|
||||
<p className="title-16 text-[var(--color-status-critical-text)] mb-2 break-keep">
|
||||
오류가 발생했습니다
|
||||
</p>
|
||||
<p className="body-14 text-neutral-60 break-keep">
|
||||
{error ?? "마케팅 플랜을 찾을 수 없습니다."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-plan-content data-plan-id={data.id}>
|
||||
<PlanHeaderSection />
|
||||
<PlanBrandingGuideSection />
|
||||
<PlanChannelStrategySection />
|
||||
<PlanContentStrategySection />
|
||||
<PlanContentCalendarSection />
|
||||
<PlanAssetCollectionSection />
|
||||
<PlanMyAssetUploadSection />
|
||||
<PlanCtaSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,34 @@
|
|||
import { ReportPage as ReportFeaturePage } from "@/features/report/ui/ReportPage";
|
||||
import { useReportSubNav } from "@/features/report/hooks/useReportSubNav";
|
||||
import { ReportOverviewSection } from "@/features/report/ui/ReportOverviewSection";
|
||||
import { ReportClinicSection } from "@/features/report/ui/ReportClinicSection";
|
||||
import { ReportChannelsSection } from "@/features/report/ui/ReportChannelsSection";
|
||||
import { ReportYouTubeSection } from "@/features/report/ui/ReportYouTubeSection";
|
||||
import { ReportInstagramSection } from "@/features/report/ui/ReportInstagramSection";
|
||||
import { ReportFacebookSection } from "@/features/report/ui/ReportFacebookSection";
|
||||
import { ReportOtherChannelsSection } from "@/features/report/ui/ReportOtherChannelsSection";
|
||||
import { ReportDiagnosisSection } from "@/features/report/ui/ReportDiagnosisSection";
|
||||
import { ReportTransformationSection } from "@/features/report/ui/ReportTransformationSection";
|
||||
import { ReportRoadmapSection } from "@/features/report/ui/ReportRoadmapSection";
|
||||
import { ReportKpiSection } from "@/features/report/ui/ReportKpiSection";
|
||||
|
||||
export function ReportPage() {
|
||||
return <ReportFeaturePage />;
|
||||
useReportSubNav();
|
||||
|
||||
return (
|
||||
<div data-report-content>
|
||||
<div className="divide-y divide-neutral-20">
|
||||
<ReportOverviewSection />
|
||||
<ReportClinicSection />
|
||||
<ReportChannelsSection />
|
||||
<ReportYouTubeSection />
|
||||
<ReportInstagramSection />
|
||||
<ReportFacebookSection />
|
||||
<ReportOtherChannelsSection />
|
||||
<ReportDiagnosisSection />
|
||||
<ReportTransformationSection />
|
||||
<ReportRoadmapSection />
|
||||
<ReportKpiSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
// 공통 API 클라이언트 설정 (Axios 인스턴스, 인터셉터 등)
|
||||
// TODO: Phase 1 백엔드 연동 시 구현
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
// 전역 상태 관리 (사용자 세션, 테마 등)
|
||||
// TODO: Phase 1 백엔드 연동 시 구현
|
||||
Loading…
Reference in New Issue