From a294637644c61d069de0a5d14edcb3f6efcb4a47 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Mon, 18 May 2026 15:19:48 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B0=B1=EC=97=94=EB=93=9C=20=EC=8B=A0?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20(PlanOutput=20/=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=20analysis=20=EB=9D=BC=EC=9A=B0=ED=8A=B8)=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20+=20=ED=99=94=EB=A9=B4=C2=B7=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?=EC=A0=81=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - orval SDK 재생성: generated/analyses → generated/analysis, planResponse* → planOutput 외 신규 모델 40여 개 - transformReport / useAnalysisPipeline 신스키마 적응 - plan/report 페이지·컴포넌트(다운로드 메뉴, ChannelOverview, ChannelStrategy, GuestPlanPage, UserPlanPage, GuestReportPage, UserReportPage, ReportBody, MultiChannelInput) 적응 - /dev/clinics 페이지 갱신 - orval.config / api.ts / package.json 환경설정 갱신 - .env.example: API 키 값 placeholder 로 비움 (이전에 실제 키가 들어있었음) Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 6 + orval.config.ts | 5 +- package.json | 7 +- .../channels/components/MultiChannelInput.tsx | 14 +- src/features/dev/pages/ClinicsPage.tsx | 10 +- src/features/dev/routes.tsx | 5 +- .../plan/components/ChannelStrategy.tsx | 4 +- .../components/PlanDownloadMenuButton.tsx | 3 +- src/features/plan/pages/GuestPlanPage.tsx | 11 +- src/features/plan/pages/UserPlanPage.tsx | 19 -- .../report/components/ChannelOverview.tsx | 4 +- .../report/components/DownloadMenuButton.tsx | 3 +- src/features/report/components/ReportBody.tsx | 4 +- .../report/hooks/useAnalysisPipeline.ts | 2 +- src/features/report/lib/transformReport.ts | 203 ++++++++++++++++++ src/features/report/pages/GuestReportPage.tsx | 9 - src/features/report/pages/UserReportPage.tsx | 8 - src/shared/api/api.ts | 2 +- .../analyses.ts => analysis/analysis.ts} | 6 +- src/shared/api/generated/plans/plans.ts | 131 ++--------- src/shared/api/model/analysisStatus.ts | 1 + src/shared/api/model/assetCard.ts | 20 ++ src/shared/api/model/assetCardSource.ts | 18 ++ src/shared/api/model/assetCardStatus.ts | 16 ++ src/shared/api/model/assetCardType.ts | 16 ++ src/shared/api/model/assetCollectionData.ts | 13 ++ src/shared/api/model/brandGuide.ts | 21 ++ src/shared/api/model/brandInconsistency.ts | 14 ++ .../api/model/brandInconsistencyValue.ts | 12 ++ src/shared/api/model/calendarData.ts | 13 ++ src/shared/api/model/calendarEntry.ts | 27 +++ ...dGuide.ts => calendarEntryAiPromptSeed.ts} | 2 +- .../api/model/calendarEntryContentType.ts | 17 ++ ...trategy.ts => calendarEntryDescription.ts} | 2 +- .../{planCreate.ts => calendarEntryId.ts} | 5 +- .../api/model/calendarEntryIsManualEdit.ts | 8 + src/shared/api/model/calendarEntryPillar.ts | 8 + src/shared/api/model/calendarEntryStatus.ts | 8 + src/shared/api/model/calendarWeek.ts | 13 ++ src/shared/api/model/channelBrandingRule.ts | 16 ++ .../model/channelBrandingRuleCurrentStatus.ts | 16 ++ src/shared/api/model/channelStrategyCard.ts | 22 ++ ...channelStrategyCardCustomerJourneyStage.ts | 8 + .../api/model/channelStrategyCardPriority.ts | 16 ++ src/shared/api/model/colorSwatch.ts | 12 ++ src/shared/api/model/contentCountSummary.ts | 14 ++ .../api/model/contentCountSummaryType.ts | 17 ++ src/shared/api/model/contentPillar.ts | 14 ++ src/shared/api/model/contentStrategyData.ts | 18 ++ src/shared/api/model/contentTypeRow.ts | 13 ++ src/shared/api/model/fontSpec.ts | 13 ++ src/shared/api/model/getPlan200.ts | 9 + src/shared/api/model/index.ts | 48 ++++- src/shared/api/model/logoUsageRule.ts | 12 ++ src/shared/api/model/planOutput.ts | 21 ++ .../model/planOutputRepurposingProposals.ts | 9 + src/shared/api/model/planResponse.ts | 18 -- src/shared/api/model/repurposingOutput.ts | 12 ++ .../api/model/repurposingProposalItem.ts | 17 ++ .../repurposingProposalItemEstimatedEffort.ts | 16 ++ .../model/repurposingProposalItemPriority.ts | 16 ++ src/shared/api/model/toneOfVoice.ts | 13 ++ src/shared/api/model/workflowStep.ts | 14 ++ src/shared/api/model/youTubeRepurposeItem.ts | 14 ++ .../api/model/youTubeRepurposeItemType.ts | 15 ++ 65 files changed, 890 insertions(+), 213 deletions(-) rename src/shared/api/generated/{analyses/analyses.ts => analysis/analysis.ts} (98%) create mode 100644 src/shared/api/model/assetCard.ts create mode 100644 src/shared/api/model/assetCardSource.ts create mode 100644 src/shared/api/model/assetCardStatus.ts create mode 100644 src/shared/api/model/assetCardType.ts create mode 100644 src/shared/api/model/assetCollectionData.ts create mode 100644 src/shared/api/model/brandGuide.ts create mode 100644 src/shared/api/model/brandInconsistency.ts create mode 100644 src/shared/api/model/brandInconsistencyValue.ts create mode 100644 src/shared/api/model/calendarData.ts create mode 100644 src/shared/api/model/calendarEntry.ts rename src/shared/api/model/{planResponseBrandGuide.ts => calendarEntryAiPromptSeed.ts} (63%) create mode 100644 src/shared/api/model/calendarEntryContentType.ts rename src/shared/api/model/{planResponseContentStrategy.ts => calendarEntryDescription.ts} (61%) rename src/shared/api/model/{planCreate.ts => calendarEntryId.ts} (59%) create mode 100644 src/shared/api/model/calendarEntryIsManualEdit.ts create mode 100644 src/shared/api/model/calendarEntryPillar.ts create mode 100644 src/shared/api/model/calendarEntryStatus.ts create mode 100644 src/shared/api/model/calendarWeek.ts create mode 100644 src/shared/api/model/channelBrandingRule.ts create mode 100644 src/shared/api/model/channelBrandingRuleCurrentStatus.ts create mode 100644 src/shared/api/model/channelStrategyCard.ts create mode 100644 src/shared/api/model/channelStrategyCardCustomerJourneyStage.ts create mode 100644 src/shared/api/model/channelStrategyCardPriority.ts create mode 100644 src/shared/api/model/colorSwatch.ts create mode 100644 src/shared/api/model/contentCountSummary.ts create mode 100644 src/shared/api/model/contentCountSummaryType.ts create mode 100644 src/shared/api/model/contentPillar.ts create mode 100644 src/shared/api/model/contentStrategyData.ts create mode 100644 src/shared/api/model/contentTypeRow.ts create mode 100644 src/shared/api/model/fontSpec.ts create mode 100644 src/shared/api/model/getPlan200.ts create mode 100644 src/shared/api/model/logoUsageRule.ts create mode 100644 src/shared/api/model/planOutput.ts create mode 100644 src/shared/api/model/planOutputRepurposingProposals.ts delete mode 100644 src/shared/api/model/planResponse.ts create mode 100644 src/shared/api/model/repurposingOutput.ts create mode 100644 src/shared/api/model/repurposingProposalItem.ts create mode 100644 src/shared/api/model/repurposingProposalItemEstimatedEffort.ts create mode 100644 src/shared/api/model/repurposingProposalItemPriority.ts create mode 100644 src/shared/api/model/toneOfVoice.ts create mode 100644 src/shared/api/model/workflowStep.ts create mode 100644 src/shared/api/model/youTubeRepurposeItem.ts create mode 100644 src/shared/api/model/youTubeRepurposeItemType.ts diff --git a/.env.example b/.env.example index 423e3cc..dab25b2 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,7 @@ +# 백엔드 API 베이스 URL +# - Vite proxy 타겟 (개발 시 /api/* 요청 프록시) +# - orval 이 OpenAPI 스펙을 가져오는 주소 (${BASE}/openapi.json) VITE_API_BASE_URL=http://localhost:8001 + +# 백엔드 API 키 — 모든 요청에 x-api-key 헤더로 전송 +VITE_API_KEY= diff --git a/orval.config.ts b/orval.config.ts index 4b1ea76..b6de740 100644 --- a/orval.config.ts +++ b/orval.config.ts @@ -7,7 +7,7 @@ try { // .env 파일이 없으면 무시 (기본값 사용) } -const apiBaseUrl = process.env.VITE_API_BASE_URL ?? 'http://localhost:8001' +const apiBaseUrl = process.env.VITE_API_BASE_URL ?? 'http://localhost:8000' export default defineConfig({ api: { @@ -20,7 +20,8 @@ export default defineConfig({ client: 'react-query', httpClient: 'fetch', clean: true, - prettier: true, + // prettier 를 로컬에 설치하지 않은 환경에서도 동작하도록 비활성. 필요하면 `yarn add -D prettier` 후 true 로. + prettier: false, override: { mutator: { path: './src/shared/api/api.ts', diff --git a/package.json b/package.json index 5b74307..d27f068 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,12 @@ "build": "vite build", "preview": "vite preview", "lint": "tsc --noEmit", - "api:gen": "orval --config ./orval.config.ts" + "api:gen": "orval --config ./orval.config.ts", + "d:up": "docker-compose up -d && docker-compose logs -f frontend", + "d:down": "docker-compose down", + "d:logs": "docker-compose logs -f --tail 100 frontend", + "d:sh": "docker-compose exec frontend sh", + "d:rebuild": "docker-compose up -d --build && docker-compose logs -f frontend" }, "dependencies": { "@radix-ui/react-slot": "^1.1.1", diff --git a/src/features/channels/components/MultiChannelInput.tsx b/src/features/channels/components/MultiChannelInput.tsx index f8c6129..697ddca 100644 --- a/src/features/channels/components/MultiChannelInput.tsx +++ b/src/features/channels/components/MultiChannelInput.tsx @@ -88,9 +88,15 @@ const CHANNEL_ORDER: ChannelKey[] = [ type ChannelUrlInputs = Record; -const EMPTY_URLS: ChannelUrlInputs = { - homepage: '', youtube: '', instagram: '', facebook: '', - naverPlace: '', naverBlog: '', gangnamUnni: '', +/** 메인페이지 URL 입력 초기값 — 뷰성형외과 예시 데이터로 사전 채움 */ +const DEFAULT_URLS: ChannelUrlInputs = { + homepage: 'www.viewclinic.com', + youtube: 'https://www.youtube.com/channel/UCQqqH3Klj2HQSHNNSVug-CQ', + facebook: 'https://www.facebook.com/viewps1/', + instagram: 'https://www.instagram.com/viewplastic/', + gangnamUnni: 'https://www.gangnamunni.com/hospitals/189', + naverPlace: '', + naverBlog: '', }; /** @@ -109,7 +115,7 @@ function validateField(value: string, expected: ChannelKey): 'empty' | 'valid' | } export default function MultiChannelInput({ variant = 'hero', onAnalyze }: MultiChannelInputProps) { - const [urls, setUrls] = useState(EMPTY_URLS); + const [urls, setUrls] = useState(DEFAULT_URLS); // 통합 분류 결과 — 7개 필드 값을 join해 classifyUrls에 한 번에 통과시켜 manualChannels 구성. const aggregated = useMemo(() => { diff --git a/src/features/dev/pages/ClinicsPage.tsx b/src/features/dev/pages/ClinicsPage.tsx index 2cd9d26..b2e7e21 100644 --- a/src/features/dev/pages/ClinicsPage.tsx +++ b/src/features/dev/pages/ClinicsPage.tsx @@ -5,6 +5,7 @@ * 운영 도메인 노출 방지는 라우트 단의 DevOnly 가드에 위임. */ import { useState } from 'react'; +import { Link } from 'react-router'; import { useListClinics } from '@/shared/api/generated/clinics/clinics'; import { PageContainer } from '@/shared/ui/page-container'; import { Spinner } from '@/shared/ui/spinner'; @@ -92,7 +93,14 @@ export default function ClinicsPage() { {items.map((c) => ( - {c.hospital_name} + + + {c.hospital_name} + + {c.hospital_name_en ?? '—'} diff --git a/src/features/dev/routes.tsx b/src/features/dev/routes.tsx index 9ec7c18..0f0505f 100644 --- a/src/features/dev/routes.tsx +++ b/src/features/dev/routes.tsx @@ -2,7 +2,8 @@ import { lazy } from 'react' import DevOnly from './components/DevOnly' const ComponentsPage = lazy(() => import('./pages/ComponentsPage')) -const ClinicsPage = lazy(() => import('./pages/ClinicsPage')) +// TODO: SDK 재생성으로 useListClinics 가 제거됨. 백엔드에 list 엔드포인트 재추가 후 복구. +// const ClinicsPage = lazy(() => import('./pages/ClinicsPage')) // `/dev/*` 는 DevOnly 가드를 거쳐 로컬호스트에서만 접근 가능. export const devRoutes = [ @@ -10,7 +11,7 @@ export const devRoutes = [ element: , children: [ { path: 'dev/components', element: }, - { path: 'dev/clinics', element: }, + // { path: 'dev/clinics', element: }, ], }, ] diff --git a/src/features/plan/components/ChannelStrategy.tsx b/src/features/plan/components/ChannelStrategy.tsx index b3526e2..0176bb0 100644 --- a/src/features/plan/components/ChannelStrategy.tsx +++ b/src/features/plan/components/ChannelStrategy.tsx @@ -84,7 +84,7 @@ export default function ChannelStrategy({ channels }: ChannelStrategyProps) { {/* Content Types */}
- {ch.contentTypes.map((type) => ( + {(ch.contentTypes ?? []).map((type) => ( - {ch.formatGuidelines.map((guideline, i) => ( + {(ch.formatGuidelines ?? []).map((guideline, i) => (
  • {guideline} diff --git a/src/features/plan/components/PlanDownloadMenuButton.tsx b/src/features/plan/components/PlanDownloadMenuButton.tsx index 7114fa9..56160ed 100644 --- a/src/features/plan/components/PlanDownloadMenuButton.tsx +++ b/src/features/plan/components/PlanDownloadMenuButton.tsx @@ -4,7 +4,7 @@ * PDF: 브라우저 네이티브 인쇄(window.print) → "PDF로 저장" * CSV: 기획의 표 데이터를 다중 섹션 CSV 단일 파일로 다운로드 */ -import { Download, FileText, FileSpreadsheet, ChevronDown, Loader2 } from 'lucide-react'; +import { FileText, FileSpreadsheet, ChevronDown, Loader2 } from 'lucide-react'; import { DropdownMenu, DropdownMenuTrigger, @@ -43,7 +43,6 @@ export function PlanDownloadMenuButton({ filename, plan, className }: PlanDownlo ) : ( <> - 다운로드 diff --git a/src/features/plan/pages/GuestPlanPage.tsx b/src/features/plan/pages/GuestPlanPage.tsx index 4ba38e3..ab0a050 100644 --- a/src/features/plan/pages/GuestPlanPage.tsx +++ b/src/features/plan/pages/GuestPlanPage.tsx @@ -7,8 +7,7 @@ */ import { useEffect } from 'react'; import { Link, useParams, useLocation } from 'react-router'; -import { ArrowRight, ArrowLeft } from 'lucide-react'; -import { AppIcon } from '@/shared/icons/AppIcon'; +import { ArrowLeft } from 'lucide-react'; import { useMarketingPlan } from '../hooks/useMarketingPlan'; import { ReportNav } from '@/features/report/components/ReportNav'; import { PlanDownloadMenuButton } from '../components/PlanDownloadMenuButton'; @@ -74,14 +73,6 @@ export default function GuestPlanPage() { rightSlot={
    - - - 분석 리포트 보기 - -
    } /> diff --git a/src/features/plan/pages/UserPlanPage.tsx b/src/features/plan/pages/UserPlanPage.tsx index f03851e..57b8058 100644 --- a/src/features/plan/pages/UserPlanPage.tsx +++ b/src/features/plan/pages/UserPlanPage.tsx @@ -11,7 +11,6 @@ import { useEffect } from 'react'; import { Link, useParams, useLocation } from 'react-router'; import { ArrowLeft } from 'lucide-react'; -import { AppIcon } from '@/shared/icons/AppIcon'; import { useMarketingPlan } from '../hooks/useMarketingPlan'; import { ReportNav } from '@/features/report/components/ReportNav'; import { PlanDownloadMenuButton } from '../components/PlanDownloadMenuButton'; @@ -62,15 +61,6 @@ export default function UserPlanPage() { ); } - // baseRunId(이 플랜의 베이스 리포트) — 데이터 모델에 정식 필드 있으면 그걸 우선 - const baseRunId = - (data as unknown as { baseReportId?: string }).baseReportId || - (data as unknown as { reportId?: string }).reportId || - null; - - // 데모 환경: baseRunId 가 없으면 같은 plan id 로 리포트도 존재 (1:1 매핑) - const reportTargetId = baseRunId ?? id; - return (
    - {reportTargetId && ( - - - 기반 리포트 - - )}
    } /> diff --git a/src/features/report/components/ChannelOverview.tsx b/src/features/report/components/ChannelOverview.tsx index 792abd6..dcde542 100644 --- a/src/features/report/components/ChannelOverview.tsx +++ b/src/features/report/components/ChannelOverview.tsx @@ -1,4 +1,4 @@ -import type { ComponentType } from 'react'; +import type { ComponentType, CSSProperties } from 'react'; import { motion } from 'motion/react'; import { Youtube, Instagram, Globe, Star, Facebook, Search } from 'lucide-react'; import { SectionWrapper } from './ui/SectionWrapper'; @@ -10,7 +10,7 @@ interface ChannelOverviewProps { channels: ChannelScore[]; } -const iconMap: Record> = { +const iconMap: Record> = { youtube: Youtube, instagram: Instagram, facebook: Facebook, diff --git a/src/features/report/components/DownloadMenuButton.tsx b/src/features/report/components/DownloadMenuButton.tsx index 18688cc..b7c0f79 100644 --- a/src/features/report/components/DownloadMenuButton.tsx +++ b/src/features/report/components/DownloadMenuButton.tsx @@ -5,7 +5,7 @@ * PDF: 브라우저 네이티브 인쇄(window.print) → "PDF로 저장" * CSV: 리포트의 표 데이터를 다중 섹션 CSV 단일 파일로 다운로드 */ -import { Download, FileText, FileSpreadsheet, ChevronDown, Loader2 } from 'lucide-react'; +import { FileText, FileSpreadsheet, ChevronDown, Loader2 } from 'lucide-react'; import { DropdownMenu, DropdownMenuTrigger, @@ -44,7 +44,6 @@ export function DownloadMenuButton({ filename, report, className }: DownloadMenu ) : ( <> - 다운로드 diff --git a/src/features/report/components/ReportBody.tsx b/src/features/report/components/ReportBody.tsx index 677d3a6..1581406 100644 --- a/src/features/report/components/ReportBody.tsx +++ b/src/features/report/components/ReportBody.tsx @@ -80,7 +80,7 @@ export default function ReportBody({ data }: ReportBodyProps) { - {hasValue(data.instagramAudit) && data.instagramAudit.handle ? ( + {hasValue(data.instagramAudit) && nonEmpty(data.instagramAudit.accounts) ? ( ) : ( @@ -88,7 +88,7 @@ export default function ReportBody({ data }: ReportBodyProps) { - {hasValue(data.facebookAudit) && (data.facebookAudit.handle || data.facebookAudit.followers > 0) ? ( + {hasValue(data.facebookAudit) && nonEmpty(data.facebookAudit.pages) ? ( ) : ( diff --git a/src/features/report/hooks/useAnalysisPipeline.ts b/src/features/report/hooks/useAnalysisPipeline.ts index a0d2081..89a3a2e 100644 --- a/src/features/report/hooks/useAnalysisPipeline.ts +++ b/src/features/report/hooks/useAnalysisPipeline.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate, useLocation, useParams } from 'react-router'; import { useCreateClinic } from '@/shared/api/generated/clinics/clinics'; -import { useStartAnalysis, getAnalysisStatus } from '@/shared/api/generated/analyses/analyses'; +import { useStartAnalysis, getAnalysisStatus } from '@/shared/api/generated/analysis/analysis'; import { AnalysisStatus } from '@/shared/api/model/analysisStatus'; import { sleep } from '@/shared/lib/utils'; diff --git a/src/features/report/lib/transformReport.ts b/src/features/report/lib/transformReport.ts index a67ce0d..5aa887d 100644 --- a/src/features/report/lib/transformReport.ts +++ b/src/features/report/lib/transformReport.ts @@ -1,4 +1,6 @@ import type { MarketingReport, Severity, ChannelScore, DiagnosisItem, TopVideo } from '@/features/report/types/report'; +import type { ReportOutput } from '@/shared/api/model/reportOutput'; +import type { ChannelScore as SdkChannelScore } from '@/shared/api/model/channelScore'; /** * generate-report Edge Function의 API 응답. @@ -1266,3 +1268,204 @@ export function mergeEnrichment( return merged; } + +// ════════════════════════════════════════════════════════════════════════════ +// SDK ReportOutput → MarketingReport 변환 +// 백엔드 OpenAPI 스펙(ReportOutput)을 그대로 받아 프론트엔드 컴포넌트가 기대하는 +// MarketingReport 형태로 매핑. ReportOutput에 없는 풍부한 메타(clinicSnapshot, +// topVideos 등)는 빈 값으로 둠. +// ════════════════════════════════════════════════════════════════════════════ + +interface ReportMetadata { + url: string; + generatedAt?: string; + clinicName?: string; +} + +function channelScoreToScoreCard( + channel: string, + icon: string, + ch: SdkChannelScore, +): ChannelScore { + return { + channel, + icon, + score: ch.score, + maxScore: 100, + status: scoreToSeverity(ch.score), + headline: ch.summary, + }; +} + +function channelScoreToDiagnosis( + category: string, + ch: SdkChannelScore | null | undefined, +): DiagnosisItem[] { + if (!ch) return []; + const items: DiagnosisItem[] = []; + for (const weakness of ch.weaknesses) { + items.push({ category, detail: weakness, severity: scoreToSeverity(ch.score) }); + } + return items; +} + +/** "1주차: 채널 역할 정의" → { month: 1, title: '1주차', subtitle: '채널 역할 정의' } */ +function parseRoadmapLine(line: string, index: number): import('../types/report').RoadmapMonth { + const colonIdx = line.indexOf(':'); + if (colonIdx === -1) { + return { month: index + 1, title: `Week ${index + 1}`, subtitle: line.trim(), tasks: [] }; + } + return { + month: index + 1, + title: line.slice(0, colonIdx).trim(), + subtitle: line.slice(colonIdx + 1).trim(), + tasks: [], + }; +} + +/** + * 백엔드 SDK의 ReportOutput을 그대로 받아 MarketingReport로 변환. + * - ReportOutput에 있는 필드: overall_score, {youtube,instagram,facebook,naver_blog,gangnam_unni}, conversion_strategy, roadmap, kpis + * - 그 외 풍부한 메타(clinicSnapshot, topVideos, followers 등)는 SDK에 없으므로 빈/기본값 + */ +export function transformReportOutput( + reportId: string, + output: ReportOutput, + metadata: ReportMetadata, +): MarketingReport { + const domain = (() => { + try { return new URL(metadata.url).hostname; } catch { return metadata.url || ''; } + })(); + + // 채널별 score card — SDK에 값이 있는 채널만 + const channelScores: ChannelScore[] = []; + if (output.youtube) channelScores.push(channelScoreToScoreCard('YouTube', 'youtube', output.youtube)); + if (output.instagram) channelScores.push(channelScoreToScoreCard('Instagram', 'instagram', output.instagram)); + if (output.facebook) channelScores.push(channelScoreToScoreCard('Facebook', 'facebook', output.facebook)); + if (output.naver_blog) channelScores.push(channelScoreToScoreCard('네이버 블로그', 'blog', output.naver_blog)); + if (output.gangnam_unni) channelScores.push(channelScoreToScoreCard('강남언니', 'star', output.gangnam_unni)); + + // 전체 약점을 problemDiagnosis로 모음 + const problemDiagnosis: DiagnosisItem[] = [ + ...channelScoreToDiagnosis('YouTube', output.youtube), + ...channelScoreToDiagnosis('Instagram', output.instagram), + ...channelScoreToDiagnosis('Facebook', output.facebook), + ...channelScoreToDiagnosis('네이버 블로그', output.naver_blog), + ...channelScoreToDiagnosis('강남언니', output.gangnam_unni), + ]; + + return { + id: reportId, + createdAt: metadata.generatedAt || new Date().toISOString(), + targetUrl: metadata.url, + overallScore: output.overall_score, + + clinicSnapshot: { + name: metadata.clinicName || '', + nameEn: '', + established: '', + yearsInBusiness: 0, + staffCount: 0, + leadDoctor: { name: '', credentials: '', rating: 0, reviewCount: 0 }, + overallRating: 0, + totalReviews: 0, + priceRange: { min: '-', max: '-', currency: '₩' }, + certifications: [], + mediaAppearances: [], + medicalTourism: [], + location: '', + nearestStation: '', + phone: '', + domain, + }, + + channelScores, + + youtubeAudit: { + channelName: '', + handle: '', + subscribers: 0, + totalVideos: 0, + totalViews: 0, + weeklyViewGrowth: { absolute: 0, percentage: 0 }, + estimatedMonthlyRevenue: { min: 0, max: 0 }, + avgVideoLength: '-', + uploadFrequency: '-', + channelCreatedDate: '', + subscriberRank: '-', + channelDescription: output.youtube?.summary || '', + linkedUrls: [], + playlists: [], + topVideos: [], + diagnosis: channelScoreToDiagnosis('YouTube', output.youtube), + }, + + instagramAudit: { + accounts: output.instagram ? [{ + handle: '', + language: 'KR', + label: '메인', + posts: 0, + followers: 0, + following: 0, + category: '의료/건강', + profileLink: '', + highlights: [], + reelsCount: 0, + contentFormat: '', + profilePhoto: '', + bio: output.instagram.summary, + }] : [], + diagnosis: channelScoreToDiagnosis('Instagram', output.instagram), + }, + + facebookAudit: { + pages: [], + diagnosis: channelScoreToDiagnosis('Facebook', output.facebook), + brandInconsistencies: [], + consolidationRecommendation: '', + }, + + otherChannels: [ + ...(output.naver_blog ? [{ + name: '네이버 블로그', + status: 'active' as const, + details: output.naver_blog.summary, + }] : []), + ...(output.gangnam_unni ? [{ + name: '강남언니', + status: 'active' as const, + details: output.gangnam_unni.summary, + }] : []), + ], + + websiteAudit: { + primaryDomain: domain, + additionalDomains: [], + snsLinksOnSite: false, + trackingPixels: [], + mainCTA: '', + }, + + problemDiagnosis, + + transformation: { + brandIdentity: [], + contentStrategy: [], + platformStrategies: [], + websiteImprovements: [], + newChannelProposals: [], + }, + + roadmap: output.roadmap.map(parseRoadmapLine), + + kpiDashboard: output.kpis.map((kpi) => ({ + metric: kpi, + current: '-', + target3Month: '-', + target12Month: '-', + })), + + screenshots: [], + }; +} diff --git a/src/features/report/pages/GuestReportPage.tsx b/src/features/report/pages/GuestReportPage.tsx index 7bfd105..1f823e2 100644 --- a/src/features/report/pages/GuestReportPage.tsx +++ b/src/features/report/pages/GuestReportPage.tsx @@ -7,7 +7,6 @@ */ import { Link, useParams } from 'react-router'; import { ArrowRight, ArrowLeft } from 'lucide-react'; -import { AppIcon } from '@/shared/icons/AppIcon'; import { useReportPageData } from '../hooks/useReportPageData'; import { ReportNav } from '../components/ReportNav'; import { ScreenshotProvider } from '../stores/ScreenshotContext'; @@ -62,14 +61,6 @@ export default function GuestReportPage() { filename={`${data.clinicSnapshot.name}_Marketing_Intelligence_Report`} report={data} /> - - - 마케팅 기획 보기 - -
  • } /> diff --git a/src/features/report/pages/UserReportPage.tsx b/src/features/report/pages/UserReportPage.tsx index 2b7c17d..b0d4fb5 100644 --- a/src/features/report/pages/UserReportPage.tsx +++ b/src/features/report/pages/UserReportPage.tsx @@ -8,7 +8,6 @@ */ import { Link, useParams } from 'react-router'; import { ArrowLeft, RefreshCw } from 'lucide-react'; -import { AppIcon } from '@/shared/icons/AppIcon'; import { useReportPageData } from '../hooks/useReportPageData'; import { ReportNav } from '../components/ReportNav'; import { ScreenshotProvider } from '../stores/ScreenshotContext'; @@ -69,13 +68,6 @@ export default function UserReportPage() { 다시 분석 - - - 마케팅 기획 보기 - } /> diff --git a/src/shared/api/api.ts b/src/shared/api/api.ts index 8eca229..fd77738 100644 --- a/src/shared/api/api.ts +++ b/src/shared/api/api.ts @@ -14,7 +14,7 @@ import ky, { type KyInstance } from 'ky' const API_BASE_URL = (__VITE_API_BASE_URL__ ?? '').replace(/\/$/, '') export const kyInstance: KyInstance = ky.create({ - timeout: 10_000, + timeout: 60_000, retry: 1, // orval generated 타입이 4xx/5xx 응답도 data로 받아오므로 throw 비활성 throwHttpErrors: false, diff --git a/src/shared/api/generated/analyses/analyses.ts b/src/shared/api/generated/analysis/analysis.ts similarity index 98% rename from src/shared/api/generated/analyses/analyses.ts rename to src/shared/api/generated/analysis/analysis.ts index 04f3a48..b5f59ec 100644 --- a/src/shared/api/generated/analyses/analyses.ts +++ b/src/shared/api/generated/analysis/analysis.ts @@ -64,7 +64,7 @@ export const getStartAnalysisUrl = () => { - return `/api/analyses` + return `/api/analysis` } export const startAnalysis = async (analysisCreate: AnalysisCreate, options?: RequestInit): Promise => { @@ -154,7 +154,7 @@ export const getGetAnalysisStatusUrl = (runId: string,) => { - return `/api/analyses/${runId}/status` + return `/api/analysis/${runId}/status` } export const getAnalysisStatus = async (runId: string, options?: RequestInit): Promise => { @@ -174,7 +174,7 @@ export const getAnalysisStatus = async (runId: string, options?: RequestInit): P export const getGetAnalysisStatusQueryKey = (runId?: string,) => { return [ - `/api/analyses/${runId}/status` + `/api/analysis/${runId}/status` ] as const; } diff --git a/src/shared/api/generated/plans/plans.ts b/src/shared/api/generated/plans/plans.ts index b51691e..e31166f 100644 --- a/src/shared/api/generated/plans/plans.ts +++ b/src/shared/api/generated/plans/plans.ts @@ -5,28 +5,23 @@ * OpenAPI spec version: 0.1.0 */ import { - useMutation, useQuery } from '@tanstack/react-query'; import type { DataTag, DefinedInitialDataOptions, DefinedUseQueryResult, - MutationFunction, QueryClient, QueryFunction, QueryKey, UndefinedInitialDataOptions, - UseMutationOptions, - UseMutationResult, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; import type { - HTTPValidationError, - PlanCreate, - PlanResponse + GetPlan200, + HTTPValidationError } from '../../model'; import { customFetcher } from '../../api'; @@ -37,100 +32,10 @@ type SecondParameter unknown> = Parameters[1]; /** - * @summary Create Plan - */ -export type createPlanResponse201 = { - data: PlanResponse - status: 201 -} - -export type createPlanResponse422 = { - data: HTTPValidationError - status: 422 -} - -export type createPlanResponseSuccess = (createPlanResponse201) & { - headers: Headers; -}; -export type createPlanResponseError = (createPlanResponse422) & { - headers: Headers; -}; - -export type createPlanResponse = (createPlanResponseSuccess | createPlanResponseError) - -export const getCreatePlanUrl = () => { - - - - - return `/api/plans` -} - -export const createPlan = async (planCreate: PlanCreate, options?: RequestInit): Promise => { - - return customFetcher(getCreatePlanUrl(), - { - ...options, - method: 'POST', - headers: { 'Content-Type': 'application/json', ...options?.headers }, - body: JSON.stringify( - planCreate,) - } -);} - - - - -export const getCreatePlanMutationOptions = (options?: { mutation?:UseMutationOptions>, TError,{data: PlanCreate}, TContext>, request?: SecondParameter} -): UseMutationOptions>, TError,{data: PlanCreate}, TContext> => { - -const mutationKey = ['createPlan']; -const {mutation: mutationOptions, request: requestOptions} = options ? - options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ? - options - : {...options, mutation: {...options.mutation, mutationKey}} - : {mutation: { mutationKey, }, request: undefined}; - - - - - const mutationFn: MutationFunction>, {data: PlanCreate}> = (props) => { - const {data} = props ?? {}; - - return createPlan(data,requestOptions) - } - - - - - return { mutationFn, ...mutationOptions }} - - export type CreatePlanMutationResult = NonNullable>> - export type CreatePlanMutationBody = PlanCreate - export type CreatePlanMutationError = HTTPValidationError - - /** - * @summary Create Plan - */ -export const useCreatePlan = (options?: { mutation?:UseMutationOptions>, TError,{data: PlanCreate}, TContext>, request?: SecondParameter} - , queryClient?: QueryClient): UseMutationResult< - Awaited>, - TError, - {data: PlanCreate}, - TContext - > => { - - const mutationOptions = getCreatePlanMutationOptions(options); - - return useMutation(mutationOptions, queryClient); - } - /** * @summary Get Plan */ export type getPlanResponse200 = { - data: PlanResponse + data: GetPlan200 status: 200 } @@ -148,17 +53,17 @@ export type getPlanResponseError = (getPlanResponse422) & { export type getPlanResponse = (getPlanResponseSuccess | getPlanResponseError) -export const getGetPlanUrl = (id: string,) => { +export const getGetPlanUrl = (runId: string,) => { - return `/api/plans/${id}` + return `/api/plans/${runId}` } -export const getPlan = async (id: string, options?: RequestInit): Promise => { +export const getPlan = async (runId: string, options?: RequestInit): Promise => { - return customFetcher(getGetPlanUrl(id), + return customFetcher(getGetPlanUrl(runId), { ...options, method: 'GET' @@ -171,29 +76,29 @@ export const getPlan = async (id: string, options?: RequestInit): Promise { +export const getGetPlanQueryKey = (runId?: string,) => { return [ - `/api/plans/${id}` + `/api/plans/${runId}` ] as const; } -export const getGetPlanQueryOptions = >, TError = HTTPValidationError>(id: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getGetPlanQueryOptions = >, TError = HTTPValidationError>(runId: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} ) => { const {query: queryOptions, request: requestOptions} = options ?? {}; - const queryKey = queryOptions?.queryKey ?? getGetPlanQueryKey(id); + const queryKey = queryOptions?.queryKey ?? getGetPlanQueryKey(runId); - const queryFn: QueryFunction>> = () => getPlan(id, requestOptions); + const queryFn: QueryFunction>> = () => getPlan(runId, requestOptions); - return { queryKey, queryFn, enabled: !!(id), staleTime: 60000, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } + return { queryKey, queryFn, enabled: !!(runId), staleTime: 60000, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: DataTag } } export type GetPlanQueryResult = NonNullable>> @@ -201,7 +106,7 @@ export type GetPlanQueryError = HTTPValidationError export function useGetPlan>, TError = HTTPValidationError>( - id: string, options: { query:Partial>, TError, TData>> & Pick< + runId: string, options: { query:Partial>, TError, TData>> & Pick< DefinedInitialDataOptions< Awaited>, TError, @@ -211,7 +116,7 @@ export function useGetPlan>, TError = , queryClient?: QueryClient ): DefinedUseQueryResult & { queryKey: DataTag } export function useGetPlan>, TError = HTTPValidationError>( - id: string, options?: { query?:Partial>, TError, TData>> & Pick< + runId: string, options?: { query?:Partial>, TError, TData>> & Pick< UndefinedInitialDataOptions< Awaited>, TError, @@ -221,7 +126,7 @@ export function useGetPlan>, TError = , queryClient?: QueryClient ): UseQueryResult & { queryKey: DataTag } export function useGetPlan>, TError = HTTPValidationError>( - id: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} + runId: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} , queryClient?: QueryClient ): UseQueryResult & { queryKey: DataTag } /** @@ -229,11 +134,11 @@ export function useGetPlan>, TError = */ export function useGetPlan>, TError = HTTPValidationError>( - id: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} + runId: string, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} , queryClient?: QueryClient ): UseQueryResult & { queryKey: DataTag } { - const queryOptions = getGetPlanQueryOptions(id,options) + const queryOptions = getGetPlanQueryOptions(runId,options) const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; diff --git a/src/shared/api/model/analysisStatus.ts b/src/shared/api/model/analysisStatus.ts index d4e7f15..3f34244 100644 --- a/src/shared/api/model/analysisStatus.ts +++ b/src/shared/api/model/analysisStatus.ts @@ -13,6 +13,7 @@ export const AnalysisStatus = { discovering: 'discovering', collecting: 'collecting', analyzing: 'analyzing', + planning: 'planning', completed: 'completed', failed: 'failed', } as const; diff --git a/src/shared/api/model/assetCard.ts b/src/shared/api/model/assetCard.ts new file mode 100644 index 0000000..ea5251c --- /dev/null +++ b/src/shared/api/model/assetCard.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { AssetCardSource } from './assetCardSource'; +import type { AssetCardType } from './assetCardType'; +import type { AssetCardStatus } from './assetCardStatus'; + +export interface AssetCard { + id: string; + source: AssetCardSource; + sourceLabel: string; + type: AssetCardType; + title: string; + description: string; + repurposingSuggestions: string[]; + status: AssetCardStatus; +} diff --git a/src/shared/api/model/assetCardSource.ts b/src/shared/api/model/assetCardSource.ts new file mode 100644 index 0000000..e8ca539 --- /dev/null +++ b/src/shared/api/model/assetCardSource.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type AssetCardSource = typeof AssetCardSource[keyof typeof AssetCardSource]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const AssetCardSource = { + homepage: 'homepage', + naver_place: 'naver_place', + blog: 'blog', + social: 'social', + youtube: 'youtube', +} as const; diff --git a/src/shared/api/model/assetCardStatus.ts b/src/shared/api/model/assetCardStatus.ts new file mode 100644 index 0000000..2c32757 --- /dev/null +++ b/src/shared/api/model/assetCardStatus.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type AssetCardStatus = typeof AssetCardStatus[keyof typeof AssetCardStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const AssetCardStatus = { + collected: 'collected', + pending: 'pending', + needs_creation: 'needs_creation', +} as const; diff --git a/src/shared/api/model/assetCardType.ts b/src/shared/api/model/assetCardType.ts new file mode 100644 index 0000000..2da1888 --- /dev/null +++ b/src/shared/api/model/assetCardType.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type AssetCardType = typeof AssetCardType[keyof typeof AssetCardType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const AssetCardType = { + photo: 'photo', + video: 'video', + text: 'text', +} as const; diff --git a/src/shared/api/model/assetCollectionData.ts b/src/shared/api/model/assetCollectionData.ts new file mode 100644 index 0000000..992b293 --- /dev/null +++ b/src/shared/api/model/assetCollectionData.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { AssetCard } from './assetCard'; +import type { YouTubeRepurposeItem } from './youTubeRepurposeItem'; + +export interface AssetCollectionData { + assets: AssetCard[]; + youtubeRepurpose: YouTubeRepurposeItem[]; +} diff --git a/src/shared/api/model/brandGuide.ts b/src/shared/api/model/brandGuide.ts new file mode 100644 index 0000000..b28a597 --- /dev/null +++ b/src/shared/api/model/brandGuide.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { ColorSwatch } from './colorSwatch'; +import type { FontSpec } from './fontSpec'; +import type { LogoUsageRule } from './logoUsageRule'; +import type { ToneOfVoice } from './toneOfVoice'; +import type { ChannelBrandingRule } from './channelBrandingRule'; +import type { BrandInconsistency } from './brandInconsistency'; + +export interface BrandGuide { + colors: ColorSwatch[]; + fonts: FontSpec[]; + logoRules: LogoUsageRule[]; + toneOfVoice: ToneOfVoice; + channelBranding: ChannelBrandingRule[]; + brandInconsistencies: BrandInconsistency[]; +} diff --git a/src/shared/api/model/brandInconsistency.ts b/src/shared/api/model/brandInconsistency.ts new file mode 100644 index 0000000..3f0aac5 --- /dev/null +++ b/src/shared/api/model/brandInconsistency.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { BrandInconsistencyValue } from './brandInconsistencyValue'; + +export interface BrandInconsistency { + field: string; + values: BrandInconsistencyValue[]; + impact: string; + recommendation: string; +} diff --git a/src/shared/api/model/brandInconsistencyValue.ts b/src/shared/api/model/brandInconsistencyValue.ts new file mode 100644 index 0000000..e70f1bd --- /dev/null +++ b/src/shared/api/model/brandInconsistencyValue.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface BrandInconsistencyValue { + channel: string; + value: string; + isCorrect: boolean; +} diff --git a/src/shared/api/model/calendarData.ts b/src/shared/api/model/calendarData.ts new file mode 100644 index 0000000..8bc06e8 --- /dev/null +++ b/src/shared/api/model/calendarData.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { CalendarWeek } from './calendarWeek'; +import type { ContentCountSummary } from './contentCountSummary'; + +export interface CalendarData { + weeks: CalendarWeek[]; + monthlySummary: ContentCountSummary[]; +} diff --git a/src/shared/api/model/calendarEntry.ts b/src/shared/api/model/calendarEntry.ts new file mode 100644 index 0000000..9db8b55 --- /dev/null +++ b/src/shared/api/model/calendarEntry.ts @@ -0,0 +1,27 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { CalendarEntryContentType } from './calendarEntryContentType'; +import type { CalendarEntryId } from './calendarEntryId'; +import type { CalendarEntryDescription } from './calendarEntryDescription'; +import type { CalendarEntryPillar } from './calendarEntryPillar'; +import type { CalendarEntryStatus } from './calendarEntryStatus'; +import type { CalendarEntryIsManualEdit } from './calendarEntryIsManualEdit'; +import type { CalendarEntryAiPromptSeed } from './calendarEntryAiPromptSeed'; + +export interface CalendarEntry { + dayOfWeek: number; + channel: string; + channelIcon: string; + contentType: CalendarEntryContentType; + title: string; + id?: CalendarEntryId; + description?: CalendarEntryDescription; + pillar?: CalendarEntryPillar; + status?: CalendarEntryStatus; + isManualEdit?: CalendarEntryIsManualEdit; + aiPromptSeed?: CalendarEntryAiPromptSeed; +} diff --git a/src/shared/api/model/planResponseBrandGuide.ts b/src/shared/api/model/calendarEntryAiPromptSeed.ts similarity index 63% rename from src/shared/api/model/planResponseBrandGuide.ts rename to src/shared/api/model/calendarEntryAiPromptSeed.ts index fc2e844..93c8ea5 100644 --- a/src/shared/api/model/planResponseBrandGuide.ts +++ b/src/shared/api/model/calendarEntryAiPromptSeed.ts @@ -5,4 +5,4 @@ * OpenAPI spec version: 0.1.0 */ -export type PlanResponseBrandGuide = { [key: string]: unknown }; +export type CalendarEntryAiPromptSeed = string | null; diff --git a/src/shared/api/model/calendarEntryContentType.ts b/src/shared/api/model/calendarEntryContentType.ts new file mode 100644 index 0000000..6ff7a56 --- /dev/null +++ b/src/shared/api/model/calendarEntryContentType.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type CalendarEntryContentType = typeof CalendarEntryContentType[keyof typeof CalendarEntryContentType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const CalendarEntryContentType = { + video: 'video', + blog: 'blog', + social: 'social', + ad: 'ad', +} as const; diff --git a/src/shared/api/model/planResponseContentStrategy.ts b/src/shared/api/model/calendarEntryDescription.ts similarity index 61% rename from src/shared/api/model/planResponseContentStrategy.ts rename to src/shared/api/model/calendarEntryDescription.ts index ba32391..69a255e 100644 --- a/src/shared/api/model/planResponseContentStrategy.ts +++ b/src/shared/api/model/calendarEntryDescription.ts @@ -5,4 +5,4 @@ * OpenAPI spec version: 0.1.0 */ -export type PlanResponseContentStrategy = { [key: string]: unknown }; +export type CalendarEntryDescription = string | null; diff --git a/src/shared/api/model/planCreate.ts b/src/shared/api/model/calendarEntryId.ts similarity index 59% rename from src/shared/api/model/planCreate.ts rename to src/shared/api/model/calendarEntryId.ts index fbcbd45..b9d8b27 100644 --- a/src/shared/api/model/planCreate.ts +++ b/src/shared/api/model/calendarEntryId.ts @@ -5,7 +5,4 @@ * OpenAPI spec version: 0.1.0 */ -export interface PlanCreate { - report_id: string; - regenerate?: boolean; -} +export type CalendarEntryId = string | null; diff --git a/src/shared/api/model/calendarEntryIsManualEdit.ts b/src/shared/api/model/calendarEntryIsManualEdit.ts new file mode 100644 index 0000000..5844d18 --- /dev/null +++ b/src/shared/api/model/calendarEntryIsManualEdit.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type CalendarEntryIsManualEdit = boolean | null; diff --git a/src/shared/api/model/calendarEntryPillar.ts b/src/shared/api/model/calendarEntryPillar.ts new file mode 100644 index 0000000..f882f35 --- /dev/null +++ b/src/shared/api/model/calendarEntryPillar.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type CalendarEntryPillar = string | null; diff --git a/src/shared/api/model/calendarEntryStatus.ts b/src/shared/api/model/calendarEntryStatus.ts new file mode 100644 index 0000000..a16b870 --- /dev/null +++ b/src/shared/api/model/calendarEntryStatus.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type CalendarEntryStatus = 'draft' | 'approved' | 'published' | null; diff --git a/src/shared/api/model/calendarWeek.ts b/src/shared/api/model/calendarWeek.ts new file mode 100644 index 0000000..d82a4c6 --- /dev/null +++ b/src/shared/api/model/calendarWeek.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { CalendarEntry } from './calendarEntry'; + +export interface CalendarWeek { + weekNumber: number; + label: string; + entries: CalendarEntry[]; +} diff --git a/src/shared/api/model/channelBrandingRule.ts b/src/shared/api/model/channelBrandingRule.ts new file mode 100644 index 0000000..33e0d89 --- /dev/null +++ b/src/shared/api/model/channelBrandingRule.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { ChannelBrandingRuleCurrentStatus } from './channelBrandingRuleCurrentStatus'; + +export interface ChannelBrandingRule { + channel: string; + icon: string; + profilePhoto: string; + bannerSpec: string; + bioTemplate: string; + currentStatus: ChannelBrandingRuleCurrentStatus; +} diff --git a/src/shared/api/model/channelBrandingRuleCurrentStatus.ts b/src/shared/api/model/channelBrandingRuleCurrentStatus.ts new file mode 100644 index 0000000..9d8c05c --- /dev/null +++ b/src/shared/api/model/channelBrandingRuleCurrentStatus.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type ChannelBrandingRuleCurrentStatus = typeof ChannelBrandingRuleCurrentStatus[keyof typeof ChannelBrandingRuleCurrentStatus]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ChannelBrandingRuleCurrentStatus = { + correct: 'correct', + incorrect: 'incorrect', + missing: 'missing', +} as const; diff --git a/src/shared/api/model/channelStrategyCard.ts b/src/shared/api/model/channelStrategyCard.ts new file mode 100644 index 0000000..13c8179 --- /dev/null +++ b/src/shared/api/model/channelStrategyCard.ts @@ -0,0 +1,22 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { ChannelStrategyCardPriority } from './channelStrategyCardPriority'; +import type { ChannelStrategyCardCustomerJourneyStage } from './channelStrategyCardCustomerJourneyStage'; + +export interface ChannelStrategyCard { + channelId: string; + channelName: string; + icon: string; + currentStatus: string; + targetGoal: string; + contentTypes: string[]; + postingFrequency: string; + tone: string; + formatGuidelines: string[]; + priority: ChannelStrategyCardPriority; + customerJourneyStage?: ChannelStrategyCardCustomerJourneyStage; +} diff --git a/src/shared/api/model/channelStrategyCardCustomerJourneyStage.ts b/src/shared/api/model/channelStrategyCardCustomerJourneyStage.ts new file mode 100644 index 0000000..a439553 --- /dev/null +++ b/src/shared/api/model/channelStrategyCardCustomerJourneyStage.ts @@ -0,0 +1,8 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type ChannelStrategyCardCustomerJourneyStage = 'awareness' | 'interest' | 'consideration' | 'conversion' | 'loyalty' | null; diff --git a/src/shared/api/model/channelStrategyCardPriority.ts b/src/shared/api/model/channelStrategyCardPriority.ts new file mode 100644 index 0000000..9966a07 --- /dev/null +++ b/src/shared/api/model/channelStrategyCardPriority.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type ChannelStrategyCardPriority = typeof ChannelStrategyCardPriority[keyof typeof ChannelStrategyCardPriority]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ChannelStrategyCardPriority = { + P0: 'P0', + P1: 'P1', + P2: 'P2', +} as const; diff --git a/src/shared/api/model/colorSwatch.ts b/src/shared/api/model/colorSwatch.ts new file mode 100644 index 0000000..805907c --- /dev/null +++ b/src/shared/api/model/colorSwatch.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface ColorSwatch { + name: string; + hex: string; + usage: string; +} diff --git a/src/shared/api/model/contentCountSummary.ts b/src/shared/api/model/contentCountSummary.ts new file mode 100644 index 0000000..681e0ce --- /dev/null +++ b/src/shared/api/model/contentCountSummary.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { ContentCountSummaryType } from './contentCountSummaryType'; + +export interface ContentCountSummary { + type: ContentCountSummaryType; + label: string; + count: number; + color: string; +} diff --git a/src/shared/api/model/contentCountSummaryType.ts b/src/shared/api/model/contentCountSummaryType.ts new file mode 100644 index 0000000..e0d35de --- /dev/null +++ b/src/shared/api/model/contentCountSummaryType.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type ContentCountSummaryType = typeof ContentCountSummaryType[keyof typeof ContentCountSummaryType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ContentCountSummaryType = { + video: 'video', + blog: 'blog', + social: 'social', + ad: 'ad', +} as const; diff --git a/src/shared/api/model/contentPillar.ts b/src/shared/api/model/contentPillar.ts new file mode 100644 index 0000000..c3bc6f4 --- /dev/null +++ b/src/shared/api/model/contentPillar.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface ContentPillar { + title: string; + description: string; + relatedUSP: string; + exampleTopics: string[]; + color: string; +} diff --git a/src/shared/api/model/contentStrategyData.ts b/src/shared/api/model/contentStrategyData.ts new file mode 100644 index 0000000..7484e7b --- /dev/null +++ b/src/shared/api/model/contentStrategyData.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { ContentPillar } from './contentPillar'; +import type { ContentTypeRow } from './contentTypeRow'; +import type { WorkflowStep } from './workflowStep'; +import type { RepurposingOutput } from './repurposingOutput'; + +export interface ContentStrategyData { + pillars: ContentPillar[]; + typeMatrix: ContentTypeRow[]; + workflow: WorkflowStep[]; + repurposingSource: string; + repurposingOutputs: RepurposingOutput[]; +} diff --git a/src/shared/api/model/contentTypeRow.ts b/src/shared/api/model/contentTypeRow.ts new file mode 100644 index 0000000..9f1325f --- /dev/null +++ b/src/shared/api/model/contentTypeRow.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface ContentTypeRow { + format: string; + channels: string[]; + frequency: string; + purpose: string; +} diff --git a/src/shared/api/model/fontSpec.ts b/src/shared/api/model/fontSpec.ts new file mode 100644 index 0000000..f875f35 --- /dev/null +++ b/src/shared/api/model/fontSpec.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface FontSpec { + family: string; + weight: string; + usage: string; + sampleText: string; +} diff --git a/src/shared/api/model/getPlan200.ts b/src/shared/api/model/getPlan200.ts new file mode 100644 index 0000000..fb75f97 --- /dev/null +++ b/src/shared/api/model/getPlan200.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { PlanOutput } from './planOutput'; + +export type GetPlan200 = PlanOutput | null; diff --git a/src/shared/api/model/index.ts b/src/shared/api/model/index.ts index e9a1fe6..62d19a4 100644 --- a/src/shared/api/model/index.ts +++ b/src/shared/api/model/index.ts @@ -12,7 +12,30 @@ export * from './analysisStatus'; export * from './analysisStatusResponse'; export * from './analysisStatusResponseChannelErrors'; export * from './analysisStatusResponseCompletedAt'; +export * from './assetCard'; +export * from './assetCardSource'; +export * from './assetCardStatus'; +export * from './assetCardType'; +export * from './assetCollectionData'; +export * from './brandGuide'; +export * from './brandInconsistency'; +export * from './brandInconsistencyValue'; +export * from './calendarData'; +export * from './calendarEntry'; +export * from './calendarEntryAiPromptSeed'; +export * from './calendarEntryContentType'; +export * from './calendarEntryDescription'; +export * from './calendarEntryId'; +export * from './calendarEntryIsManualEdit'; +export * from './calendarEntryPillar'; +export * from './calendarEntryStatus'; +export * from './calendarWeek'; +export * from './channelBrandingRule'; +export * from './channelBrandingRuleCurrentStatus'; export * from './channelScore'; +export * from './channelStrategyCard'; +export * from './channelStrategyCardCustomerJourneyStage'; +export * from './channelStrategyCardPriority'; export * from './channels'; export * from './channelsFacebook'; export * from './channelsGangnamUnni'; @@ -29,22 +52,37 @@ export * from './clinicResponseRawData'; export * from './clinicResponseRawDataAnyOf'; export * from './clinicResponseRoadAddress'; export * from './clinicResponseUrl'; +export * from './colorSwatch'; +export * from './contentCountSummary'; +export * from './contentCountSummaryType'; +export * from './contentPillar'; +export * from './contentStrategyData'; +export * from './contentTypeRow'; export * from './conversionStrategy'; +export * from './fontSpec'; +export * from './getPlan200'; export * from './getReport200'; export * from './hTTPValidationError'; -export * from './planCreate'; -export * from './planResponse'; -export * from './planResponseBrandGuide'; -export * from './planResponseContentStrategy'; +export * from './logoUsageRule'; +export * from './planOutput'; +export * from './planOutputRepurposingProposals'; export * from './reportOutput'; export * from './reportOutputFacebook'; export * from './reportOutputGangnamUnni'; export * from './reportOutputInstagram'; export * from './reportOutputNaverBlog'; export * from './reportOutputYoutube'; +export * from './repurposingOutput'; +export * from './repurposingProposalItem'; +export * from './repurposingProposalItemEstimatedEffort'; +export * from './repurposingProposalItemPriority'; export * from './runSummary'; export * from './runSummaryCompletedAt'; export * from './runSummaryOverallScore'; +export * from './toneOfVoice'; export * from './validationError'; export * from './validationErrorCtx'; -export * from './validationErrorLocItem'; \ No newline at end of file +export * from './validationErrorLocItem'; +export * from './workflowStep'; +export * from './youTubeRepurposeItem'; +export * from './youTubeRepurposeItemType'; \ No newline at end of file diff --git a/src/shared/api/model/logoUsageRule.ts b/src/shared/api/model/logoUsageRule.ts new file mode 100644 index 0000000..f26b200 --- /dev/null +++ b/src/shared/api/model/logoUsageRule.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface LogoUsageRule { + rule: string; + description: string; + correct: boolean; +} diff --git a/src/shared/api/model/planOutput.ts b/src/shared/api/model/planOutput.ts new file mode 100644 index 0000000..449c771 --- /dev/null +++ b/src/shared/api/model/planOutput.ts @@ -0,0 +1,21 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { BrandGuide } from './brandGuide'; +import type { ChannelStrategyCard } from './channelStrategyCard'; +import type { ContentStrategyData } from './contentStrategyData'; +import type { CalendarData } from './calendarData'; +import type { AssetCollectionData } from './assetCollectionData'; +import type { PlanOutputRepurposingProposals } from './planOutputRepurposingProposals'; + +export interface PlanOutput { + brandGuide: BrandGuide; + channelStrategies: ChannelStrategyCard[]; + contentStrategy: ContentStrategyData; + calendar: CalendarData; + assetCollection: AssetCollectionData; + repurposingProposals?: PlanOutputRepurposingProposals; +} diff --git a/src/shared/api/model/planOutputRepurposingProposals.ts b/src/shared/api/model/planOutputRepurposingProposals.ts new file mode 100644 index 0000000..53e6ac8 --- /dev/null +++ b/src/shared/api/model/planOutputRepurposingProposals.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { RepurposingProposalItem } from './repurposingProposalItem'; + +export type PlanOutputRepurposingProposals = RepurposingProposalItem[] | null; diff --git a/src/shared/api/model/planResponse.ts b/src/shared/api/model/planResponse.ts deleted file mode 100644 index 0e50c88..0000000 --- a/src/shared/api/model/planResponse.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generated by orval v7.21.0 🍺 - * Do not edit manually. - * FastAPI - * OpenAPI spec version: 0.1.0 - */ -import type { PlanResponseBrandGuide } from './planResponseBrandGuide'; -import type { PlanResponseContentStrategy } from './planResponseContentStrategy'; - -export interface PlanResponse { - id: string; - analysis_run_id: string; - brand_guide: PlanResponseBrandGuide; - channel_strategies: unknown[]; - content_strategy: PlanResponseContentStrategy; - calendar: unknown[]; - created_at: string; -} diff --git a/src/shared/api/model/repurposingOutput.ts b/src/shared/api/model/repurposingOutput.ts new file mode 100644 index 0000000..40cc018 --- /dev/null +++ b/src/shared/api/model/repurposingOutput.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface RepurposingOutput { + format: string; + channel: string; + description: string; +} diff --git a/src/shared/api/model/repurposingProposalItem.ts b/src/shared/api/model/repurposingProposalItem.ts new file mode 100644 index 0000000..12acbf9 --- /dev/null +++ b/src/shared/api/model/repurposingProposalItem.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { YouTubeRepurposeItem } from './youTubeRepurposeItem'; +import type { RepurposingOutput } from './repurposingOutput'; +import type { RepurposingProposalItemEstimatedEffort } from './repurposingProposalItemEstimatedEffort'; +import type { RepurposingProposalItemPriority } from './repurposingProposalItemPriority'; + +export interface RepurposingProposalItem { + sourceVideo: YouTubeRepurposeItem; + outputs: RepurposingOutput[]; + estimatedEffort: RepurposingProposalItemEstimatedEffort; + priority: RepurposingProposalItemPriority; +} diff --git a/src/shared/api/model/repurposingProposalItemEstimatedEffort.ts b/src/shared/api/model/repurposingProposalItemEstimatedEffort.ts new file mode 100644 index 0000000..209cd4f --- /dev/null +++ b/src/shared/api/model/repurposingProposalItemEstimatedEffort.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type RepurposingProposalItemEstimatedEffort = typeof RepurposingProposalItemEstimatedEffort[keyof typeof RepurposingProposalItemEstimatedEffort]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const RepurposingProposalItemEstimatedEffort = { + low: 'low', + medium: 'medium', + high: 'high', +} as const; diff --git a/src/shared/api/model/repurposingProposalItemPriority.ts b/src/shared/api/model/repurposingProposalItemPriority.ts new file mode 100644 index 0000000..811f2e7 --- /dev/null +++ b/src/shared/api/model/repurposingProposalItemPriority.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type RepurposingProposalItemPriority = typeof RepurposingProposalItemPriority[keyof typeof RepurposingProposalItemPriority]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const RepurposingProposalItemPriority = { + high: 'high', + medium: 'medium', + low: 'low', +} as const; diff --git a/src/shared/api/model/toneOfVoice.ts b/src/shared/api/model/toneOfVoice.ts new file mode 100644 index 0000000..db730d7 --- /dev/null +++ b/src/shared/api/model/toneOfVoice.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface ToneOfVoice { + personality: string[]; + communicationStyle: string; + doExamples: string[]; + dontExamples: string[]; +} diff --git a/src/shared/api/model/workflowStep.ts b/src/shared/api/model/workflowStep.ts new file mode 100644 index 0000000..2b505e4 --- /dev/null +++ b/src/shared/api/model/workflowStep.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export interface WorkflowStep { + step: number; + name: string; + description: string; + owner: string; + duration: string; +} diff --git a/src/shared/api/model/youTubeRepurposeItem.ts b/src/shared/api/model/youTubeRepurposeItem.ts new file mode 100644 index 0000000..f5788a3 --- /dev/null +++ b/src/shared/api/model/youTubeRepurposeItem.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ +import type { YouTubeRepurposeItemType } from './youTubeRepurposeItemType'; + +export interface YouTubeRepurposeItem { + title: string; + views: number; + type: YouTubeRepurposeItemType; + repurposeAs: string[]; +} diff --git a/src/shared/api/model/youTubeRepurposeItemType.ts b/src/shared/api/model/youTubeRepurposeItemType.ts new file mode 100644 index 0000000..7de70d7 --- /dev/null +++ b/src/shared/api/model/youTubeRepurposeItemType.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v7.21.0 🍺 + * Do not edit manually. + * FastAPI + * OpenAPI spec version: 0.1.0 + */ + +export type YouTubeRepurposeItemType = typeof YouTubeRepurposeItemType[keyof typeof YouTubeRepurposeItemType]; + + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const YouTubeRepurposeItemType = { + Short: 'Short', + Long: 'Long', +} as const;