Compare commits
3 Commits
82f166a80e
...
84710f185c
| Author | SHA1 | Date |
|---|---|---|
|
|
84710f185c | |
|
|
e8ec9e2075 | |
|
|
0b139714ed |
|
|
@ -34,8 +34,8 @@ function formatDate(iso: string) {
|
||||||
interface AnalysisCardProps {
|
interface AnalysisCardProps {
|
||||||
clinicId: string;
|
clinicId: string;
|
||||||
run: WorkspaceRun;
|
run: WorkspaceRun;
|
||||||
/** 모든 run 은 1:1 로 연결된 plan 을 가짐 */
|
/** plan 이 있으면 1:1 매칭, 없으면 plan 미생성 상태 */
|
||||||
plan: WorkspacePlan;
|
plan?: WorkspacePlan;
|
||||||
/** 최신 row 강조 */
|
/** 최신 row 강조 */
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -44,13 +44,10 @@ export function AnalysisCard({ clinicId, run, plan, highlighted = false }: Analy
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const status = statusBadge(run.status);
|
const status = statusBadge(run.status);
|
||||||
|
|
||||||
// 가라데이터 데모 — 실제 리포트/플랜 ID 대신 view-clinic 데모 데이터로 진입.
|
// PlanPage 는 두 경로 모두에서 동일 — 워크스페이스 경로로 진입해야 향후 권한/액션 차별화 여지 확보.
|
||||||
// 실 데이터 연동 시 `/clinics/${clinicId}/report/${run.runId}` 등으로 복원.
|
void plan?.planId;
|
||||||
void clinicId;
|
const reportHref = `/report/${run.runId}`;
|
||||||
void run.runId;
|
const planHref = `/clinics/${clinicId}/plan/${run.runId}`;
|
||||||
void plan.planId;
|
|
||||||
const reportHref = '/report/view-clinic';
|
|
||||||
const planHref = '/plan/view-clinic';
|
|
||||||
|
|
||||||
const goToReport = () => navigate(reportHref);
|
const goToReport = () => navigate(reportHref);
|
||||||
const stop = (e: React.MouseEvent) => e.stopPropagation();
|
const stop = (e: React.MouseEvent) => e.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,12 @@ export function AnalysisTab({ clinicId, runs, plans }: AnalysisTabProps) {
|
||||||
return map;
|
return map;
|
||||||
}, [plans]);
|
}, [plans]);
|
||||||
|
|
||||||
|
// plan 이 없는 run 도 노출 — 백엔드 plans 엔드포인트가 채워지면 자연스럽게 매칭됨.
|
||||||
const rows = useMemo(
|
const rows = useMemo(
|
||||||
() =>
|
() =>
|
||||||
[...runs]
|
[...runs]
|
||||||
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
|
.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime())
|
||||||
.map((run) => ({ run, plan: planByRun[run.runId] }))
|
.map((run) => ({ run, plan: planByRun[run.runId] })),
|
||||||
.filter((row): row is { run: WorkspaceRun; plan: WorkspacePlan } => !!row.plan),
|
|
||||||
[runs, planByRun],
|
[runs, planByRun],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
* 분석 대상 URL/소셜 핸들 편집 + 병원 기본 정보 확인.
|
* 분석 대상 URL/소셜 핸들 편집 + 병원 기본 정보 확인.
|
||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Save, AlertCircle, Info } from 'lucide-react';
|
|
||||||
import { Input } from '@/shared/ui/input';
|
import { Input } from '@/shared/ui/input';
|
||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper';
|
import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper';
|
||||||
|
|
@ -66,7 +65,7 @@ export function SettingsTab({ clinic }: SettingsTabProps) {
|
||||||
return (
|
return (
|
||||||
<SectionWrapper
|
<SectionWrapper
|
||||||
id="settings"
|
id="settings"
|
||||||
title="분석 대상 설정"
|
title="분석 채널 설정"
|
||||||
subtitle="이 병원의 채널 정보를 등록해두면 새 분석 실행 시 자동으로 불러옵니다."
|
subtitle="이 병원의 채널 정보를 등록해두면 새 분석 실행 시 자동으로 불러옵니다."
|
||||||
>
|
>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -82,20 +81,13 @@ export function SettingsTab({ clinic }: SettingsTabProps) {
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{EDITABLE_PLATFORMS.map((platform) => {
|
{EDITABLE_PLATFORMS.map((platform) => {
|
||||||
const meta = PLATFORM_META[platform];
|
const meta = PLATFORM_META[platform];
|
||||||
const Icon = meta.Icon;
|
|
||||||
return (
|
return (
|
||||||
<div key={platform}>
|
<div key={platform}>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<label
|
<label
|
||||||
htmlFor={`platform-${platform}`}
|
htmlFor={`platform-${platform}`}
|
||||||
className="flex items-center gap-2 w-32 flex-shrink-0"
|
className="w-32 flex-shrink-0"
|
||||||
>
|
>
|
||||||
<span
|
|
||||||
className="flex items-center justify-center rounded-full w-7 h-7"
|
|
||||||
style={{ backgroundColor: `${meta.color}14` }}
|
|
||||||
>
|
|
||||||
<Icon size={14} style={{ color: meta.color }} />
|
|
||||||
</span>
|
|
||||||
<span className="text-xs font-medium text-primary-900">{meta.label}</span>
|
<span className="text-xs font-medium text-primary-900">{meta.label}</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -107,8 +99,7 @@ export function SettingsTab({ clinic }: SettingsTabProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{platform === 'website' && (
|
{platform === 'website' && (
|
||||||
<p className="ml-[140px] mt-1.5 flex items-start gap-1.5 text-[11px] text-slate-500 leading-relaxed">
|
<p className="ml-[140px] mt-1.5 text-[11px] text-slate-500 leading-relaxed">
|
||||||
<Info size={11} className="mt-0.5 shrink-0 text-brand-purple-vivid" />
|
|
||||||
홈페이지를 변경하면 다음 분석부터 병원 기본 정보(이름·주소·진료시간·의료진 등)가 새 사이트 기준으로 함께 갱신돼요.
|
홈페이지를 변경하면 다음 분석부터 병원 기본 정보(이름·주소·진료시간·의료진 등)가 새 사이트 기준으로 함께 갱신돼요.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -119,28 +110,18 @@ export function SettingsTab({ clinic }: SettingsTabProps) {
|
||||||
|
|
||||||
<div className="mt-6 flex items-center justify-between border-t border-slate-100 pt-4">
|
<div className="mt-6 flex items-center justify-between border-t border-slate-100 pt-4">
|
||||||
<p
|
<p
|
||||||
className={`inline-flex items-center gap-1.5 text-xs ${
|
className={`text-xs ${saved ? 'text-emerald-600' : 'text-slate-400'}`}
|
||||||
saved ? 'text-emerald-600' : 'text-slate-400'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{saved ? (
|
{saved
|
||||||
<>
|
? '로컬에 저장되었습니다 (API 연동 후속 작업)'
|
||||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" aria-hidden />
|
: '저장은 아직 서버에 반영되지 않습니다.'}
|
||||||
로컬에 저장되었습니다 (API 연동 후속 작업)
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<AlertCircle size={12} className="text-slate-300" />
|
|
||||||
저장은 아직 서버에 반영되지 않습니다.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="inline-flex items-center gap-1.5 px-5 py-2.5 rounded-full text-xs font-semibold text-white bg-gradient-to-r from-[#4F1DA1] to-[#021341] shadow-sm hover:opacity-90 transition-all"
|
className="px-5 py-2.5 rounded-full text-xs font-semibold text-white bg-gradient-to-r from-[#4F1DA1] to-[#021341] shadow-sm hover:opacity-90 transition-all"
|
||||||
>
|
>
|
||||||
<Save size={13} />변경 저장
|
변경 저장
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
Heart,
|
Heart,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useGetReport } from '@/shared/api/generated/reports/reports';
|
import { useGetReport } from '@/shared/api/generated/report/report';
|
||||||
import { PageContainer } from '@/shared/ui/page-container';
|
import { PageContainer } from '@/shared/ui/page-container';
|
||||||
import { CLINIC, DOCTORS, RATINGS, PROCEDURES } from '../data/mockClinicProfile';
|
import { CLINIC, DOCTORS, RATINGS, PROCEDURES } from '../data/mockClinicProfile';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import type { RouteObject } from 'react-router'
|
||||||
const ClinicProfilePage = lazy(() => import('./pages/ClinicProfilePage'))
|
const ClinicProfilePage = lazy(() => import('./pages/ClinicProfilePage'))
|
||||||
const ClinicWorkspacePage = lazy(() => import('./pages/ClinicWorkspacePage'))
|
const ClinicWorkspacePage = lazy(() => import('./pages/ClinicWorkspacePage'))
|
||||||
const UserReportPage = lazy(() => import('@/features/report/pages/UserReportPage'))
|
const UserReportPage = lazy(() => import('@/features/report/pages/UserReportPage'))
|
||||||
const UserPlanPage = lazy(() => import('@/features/plan/pages/UserPlanPage'))
|
const PlanPage = lazy(() => import('@/features/plan/pages/PlanPage'))
|
||||||
|
|
||||||
export const clinicsRoutes: RouteObject[] = [
|
export const clinicsRoutes: RouteObject[] = [
|
||||||
// 공개 프로필 (손님/누구나)
|
// 공개 프로필 (손님/누구나)
|
||||||
|
|
@ -13,5 +13,5 @@ export const clinicsRoutes: RouteObject[] = [
|
||||||
// 유저 워크스페이스 (계약 병원 전용)
|
// 유저 워크스페이스 (계약 병원 전용)
|
||||||
{ path: 'clinics/:clinicId', element: <ClinicWorkspacePage /> },
|
{ path: 'clinics/:clinicId', element: <ClinicWorkspacePage /> },
|
||||||
{ path: 'clinics/:clinicId/report/:id', element: <UserReportPage /> },
|
{ path: 'clinics/:clinicId/report/:id', element: <UserReportPage /> },
|
||||||
{ path: 'clinics/:clinicId/plan/:id', element: <UserPlanPage /> },
|
{ path: 'clinics/:clinicId/plan/:id', element: <PlanPage /> },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* Dev: 클리닉 리스트 페이지.
|
* Dev: 클리닉 리스트 페이지.
|
||||||
*
|
*
|
||||||
* 백엔드 `GET /api/clinics` (listClinics) 응답을 표로 확인하는 개발용 화면.
|
* 백엔드 `GET /api/clinics` (getClinics) 응답을 표로 확인하는 개발용 화면.
|
||||||
* 운영 도메인 노출 방지는 라우트 단의 DevOnly 가드에 위임.
|
* 운영 도메인 노출 방지는 라우트 단의 DevOnly 가드에 위임.
|
||||||
*/
|
*/
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { useListClinics } from '@/shared/api/generated/clinics/clinics';
|
import { useGetClinics } from '@/shared/api/generated/clinics/clinics';
|
||||||
import { PageContainer } from '@/shared/ui/page-container';
|
import { PageContainer } from '@/shared/ui/page-container';
|
||||||
import { Spinner } from '@/shared/ui/spinner';
|
import { Spinner } from '@/shared/ui/spinner';
|
||||||
import { EmptyState } from '@/shared/ui/empty-state';
|
import { EmptyState } from '@/shared/ui/empty-state';
|
||||||
|
|
@ -31,12 +31,13 @@ function formatDate(raw: string): string {
|
||||||
export default function ClinicsPage() {
|
export default function ClinicsPage() {
|
||||||
const [offset, setOffset] = useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
|
|
||||||
const { data, isLoading, error, refetch, isFetching } = useListClinics(
|
const { data, isLoading, error, refetch, isFetching } = useGetClinics(
|
||||||
{ limit: PAGE_SIZE, offset },
|
{ limit: PAGE_SIZE, offset },
|
||||||
{ query: { staleTime: 0 } },
|
{ query: { staleTime: 0 } },
|
||||||
);
|
);
|
||||||
|
|
||||||
const items = data?.status === 200 ? data.data : [];
|
const items = data?.status === 200 ? data.data.items : [];
|
||||||
|
const total = data?.status === 200 ? data.data.total : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-20 pb-16">
|
<div className="pt-20 pb-16">
|
||||||
|
|
@ -50,7 +51,7 @@ export default function ClinicsPage() {
|
||||||
클리닉 리스트
|
클리닉 리스트
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-slate-500 mt-1">
|
<p className="text-sm text-slate-500 mt-1">
|
||||||
GET /api/clinics · limit={PAGE_SIZE} · offset={offset}
|
GET /api/clinics · limit={PAGE_SIZE} · offset={offset} · total={total}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
@ -131,8 +132,10 @@ export default function ClinicsPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 페이지네이션 — 정확한 total 이 없어서 단순 prev/next 만 노출 */}
|
|
||||||
<div className="flex items-center justify-end gap-2 mt-4">
|
<div className="flex items-center justify-end gap-2 mt-4">
|
||||||
|
<span className="text-xs text-slate-500 mr-2">
|
||||||
|
{total > 0 ? `${offset + 1}-${Math.min(offset + items.length, total)} / ${total}` : ''}
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -145,7 +148,7 @@ export default function ClinicsPage() {
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setOffset((o) => o + PAGE_SIZE)}
|
onClick={() => setOffset((o) => o + PAGE_SIZE)}
|
||||||
disabled={items.length < PAGE_SIZE || isFetching}
|
disabled={offset + items.length >= total || isFetching}
|
||||||
>
|
>
|
||||||
다음
|
다음
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ import { lazy } from 'react'
|
||||||
import DevOnly from './components/DevOnly'
|
import DevOnly from './components/DevOnly'
|
||||||
|
|
||||||
const ComponentsPage = lazy(() => import('./pages/ComponentsPage'))
|
const ComponentsPage = lazy(() => import('./pages/ComponentsPage'))
|
||||||
// TODO: SDK 재생성으로 useListClinics 가 제거됨. 백엔드에 list 엔드포인트 재추가 후 복구.
|
const ClinicsPage = lazy(() => import('./pages/ClinicsPage'))
|
||||||
// const ClinicsPage = lazy(() => import('./pages/ClinicsPage'))
|
|
||||||
|
|
||||||
// `/dev/*` 는 DevOnly 가드를 거쳐 로컬호스트에서만 접근 가능.
|
// `/dev/*` 는 DevOnly 가드를 거쳐 로컬호스트에서만 접근 가능.
|
||||||
export const devRoutes = [
|
export const devRoutes = [
|
||||||
|
|
@ -11,7 +10,7 @@ export const devRoutes = [
|
||||||
element: <DevOnly />,
|
element: <DevOnly />,
|
||||||
children: [
|
children: [
|
||||||
{ path: 'dev/components', element: <ComponentsPage /> },
|
{ path: 'dev/components', element: <ComponentsPage /> },
|
||||||
// { path: 'dev/clinics', element: <ClinicsPage /> },
|
{ path: 'dev/clinics', element: <ClinicsPage /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper';
|
||||||
import { EmptyState } from '@/shared/ui/empty-state';
|
import { EmptyState } from '@/shared/ui/empty-state';
|
||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
import { Input } from '@/shared/ui/input';
|
import { Input } from '@/shared/ui/input';
|
||||||
import { BrandAppliedPreview } from './BrandAppliedPreview';
|
// import { BrandAppliedPreview } from './BrandAppliedPreview';
|
||||||
import {
|
import {
|
||||||
tabItems,
|
tabItems,
|
||||||
type TabKey,
|
type TabKey,
|
||||||
|
|
@ -255,9 +255,9 @@ function VisualIdentityTab({ data, clinicName }: { data: BrandGuide; clinicName:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Brand applied preview — IG/YouTube mockup */}
|
{/* Brand applied preview — IG/YouTube mockup */}
|
||||||
{data.colors.length > 0 && data.fonts.length > 0 && (
|
{/* {data.colors.length > 0 && data.fonts.length > 0 && (
|
||||||
<BrandAppliedPreview data={data} clinicName={clinicName} />
|
<BrandAppliedPreview data={data} clinicName={clinicName} />
|
||||||
)}
|
)} */}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,26 @@
|
||||||
import { useState, useRef, useCallback, type DragEvent, type ChangeEvent } from 'react';
|
import { useState, useRef, useCallback, useEffect, type DragEvent, type ChangeEvent } from 'react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper';
|
import { SectionWrapper } from '@/features/report/components/ui/SectionWrapper';
|
||||||
import { VideoFilled, FileTextFilled } from '@/shared/icons/FilledIcons';
|
import { VideoFilled, FileTextFilled } from '@/shared/icons/FilledIcons';
|
||||||
import { Button } from '@/shared/ui/button';
|
import { Button } from '@/shared/ui/button';
|
||||||
|
import { Spinner } from '@/shared/ui/spinner';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/shared/ui/dialog';
|
||||||
|
import {
|
||||||
|
useUploadAnalysisRunFile,
|
||||||
|
useGetAnalysisRunFiles,
|
||||||
|
useDeleteAnalysisRunFile,
|
||||||
|
} from '@/shared/api/generated/analysis/analysis';
|
||||||
|
import type { FileListItem } from '@/shared/api/model/fileListItem';
|
||||||
import {
|
import {
|
||||||
type UploadCategory,
|
type UploadCategory,
|
||||||
|
type AssetCategory,
|
||||||
categoryConfig,
|
categoryConfig,
|
||||||
categoryBadge,
|
categoryBadge,
|
||||||
ALL_ACCEPT,
|
ALL_ACCEPT,
|
||||||
|
|
@ -15,43 +31,141 @@ import {
|
||||||
|
|
||||||
// ─── Types ───
|
// ─── Types ───
|
||||||
|
|
||||||
|
type AssetStatus = 'idle' | 'uploading' | 'done' | 'error';
|
||||||
|
|
||||||
interface UploadedAsset {
|
interface UploadedAsset {
|
||||||
id: string;
|
id: string;
|
||||||
file: File;
|
/** 로컬 업로드 직후엔 File 보유, 서버에서 받은 항목은 null. */
|
||||||
category: 'image' | 'video' | 'text';
|
file: File | null;
|
||||||
|
category: AssetCategory;
|
||||||
previewUrl: string | null;
|
previewUrl: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
size: string;
|
size: string;
|
||||||
uploadedAt: Date;
|
uploadedAt: Date;
|
||||||
|
status: AssetStatus;
|
||||||
|
remoteId?: number;
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MyAssetUploadProps {
|
||||||
|
/** 업로드 대상 analysis run id. 없으면 로컬 미리보기만 동작. */
|
||||||
|
analysisRunId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 백엔드 FileType ('image'|'video'|'audio'|'document'|'file') → 로컬 AssetCategory 1:1.
|
||||||
|
function mapFileTypeToCategory(fileType: string): AssetCategory {
|
||||||
|
if (fileType === 'image' || fileType === 'video' || fileType === 'audio' || fileType === 'document') {
|
||||||
|
return fileType;
|
||||||
|
}
|
||||||
|
return 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverFileToAsset(item: FileListItem): UploadedAsset {
|
||||||
|
const cat = mapFileTypeToCategory(item.file_type);
|
||||||
|
return {
|
||||||
|
id: `remote-${item.id}`,
|
||||||
|
file: null,
|
||||||
|
category: cat,
|
||||||
|
previewUrl: cat === 'image' || cat === 'video' ? item.file_url : null,
|
||||||
|
name: item.file_name,
|
||||||
|
size: typeof item.size_bytes === 'number' ? formatSize(item.size_bytes) : '',
|
||||||
|
uploadedAt: new Date(item.created_at),
|
||||||
|
status: 'done',
|
||||||
|
remoteId: item.id,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Component ───
|
// ─── Component ───
|
||||||
|
|
||||||
export default function MyAssetUpload() {
|
export default function MyAssetUpload({ analysisRunId }: MyAssetUploadProps = {}) {
|
||||||
const [assets, setAssets] = useState<UploadedAsset[]>([]);
|
const [assets, setAssets] = useState<UploadedAsset[]>([]);
|
||||||
const [activeFilter, setActiveFilter] = useState<UploadCategory>('all');
|
const [activeFilter, setActiveFilter] = useState<UploadCategory>('all');
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
|
const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null);
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const processFiles = useCallback((files: FileList | File[]) => {
|
const { mutateAsync: uploadFile } = useUploadAnalysisRunFile();
|
||||||
const newAssets: UploadedAsset[] = Array.from(files).map((file) => {
|
const { mutateAsync: deleteFile } = useDeleteAnalysisRunFile();
|
||||||
const cat = categorize(file);
|
const filesQuery = useGetAnalysisRunFiles(
|
||||||
const previewUrl =
|
analysisRunId ?? '',
|
||||||
cat === 'image' || cat === 'video'
|
{ query: { enabled: !!analysisRunId } },
|
||||||
? URL.createObjectURL(file)
|
);
|
||||||
: null;
|
|
||||||
return {
|
// 서버 파일 목록 → 로컬 assets 머지. 진행 중/실패 로컬 항목은 보존.
|
||||||
id: uid(),
|
useEffect(() => {
|
||||||
file,
|
if (filesQuery.data?.status !== 200) return;
|
||||||
category: cat,
|
const serverAssets = filesQuery.data.data.map(serverFileToAsset);
|
||||||
previewUrl,
|
const serverIds = new Set(serverAssets.map((a) => a.remoteId));
|
||||||
name: file.name,
|
setAssets((prev) => {
|
||||||
size: formatSize(file.size),
|
const localPending = prev.filter(
|
||||||
uploadedAt: new Date(),
|
(a) => a.remoteId == null || !serverIds.has(a.remoteId),
|
||||||
};
|
);
|
||||||
|
return [...localPending, ...serverAssets];
|
||||||
});
|
});
|
||||||
setAssets((prev) => [...newAssets, ...prev]);
|
}, [filesQuery.data]);
|
||||||
}, []);
|
|
||||||
|
const processFiles = useCallback(
|
||||||
|
(files: FileList | File[]) => {
|
||||||
|
const newAssets: UploadedAsset[] = Array.from(files).map((file) => {
|
||||||
|
const cat = categorize(file);
|
||||||
|
const previewUrl =
|
||||||
|
cat === 'image' || cat === 'video' ? URL.createObjectURL(file) : null;
|
||||||
|
return {
|
||||||
|
id: uid(),
|
||||||
|
file,
|
||||||
|
category: cat,
|
||||||
|
previewUrl,
|
||||||
|
name: file.name,
|
||||||
|
size: formatSize(file.size),
|
||||||
|
uploadedAt: new Date(),
|
||||||
|
status: analysisRunId ? 'uploading' : 'idle',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setAssets((prev) => [...newAssets, ...prev]);
|
||||||
|
|
||||||
|
if (!analysisRunId) return;
|
||||||
|
|
||||||
|
newAssets.forEach(async (asset) => {
|
||||||
|
try {
|
||||||
|
const res = await uploadFile({
|
||||||
|
runId: analysisRunId,
|
||||||
|
// orval 이 binary 필드를 string 으로 타입 생성하므로 캐스팅 필요. 런타임은 FormData 로 정상 처리.
|
||||||
|
data: { file: asset.file as unknown as string, file_type: asset.category },
|
||||||
|
});
|
||||||
|
if (res.status === 201) {
|
||||||
|
setAssets((prev) =>
|
||||||
|
prev.map((a) =>
|
||||||
|
a.id === asset.id ? { ...a, status: 'done', remoteId: res.data.id } : a,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
filesQuery.refetch();
|
||||||
|
} else {
|
||||||
|
setAssets((prev) =>
|
||||||
|
prev.map((a) =>
|
||||||
|
a.id === asset.id
|
||||||
|
? { ...a, status: 'error', errorMessage: '업로드 검증 실패' }
|
||||||
|
: a,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setAssets((prev) =>
|
||||||
|
prev.map((a) =>
|
||||||
|
a.id === asset.id
|
||||||
|
? {
|
||||||
|
...a,
|
||||||
|
status: 'error',
|
||||||
|
errorMessage: err instanceof Error ? err.message : '업로드 실패',
|
||||||
|
}
|
||||||
|
: a,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[analysisRunId, uploadFile, filesQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(e: DragEvent) => {
|
(e: DragEvent) => {
|
||||||
|
|
@ -72,22 +186,48 @@ export default function MyAssetUpload() {
|
||||||
[processFiles],
|
[processFiles],
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeAsset = useCallback((id: string) => {
|
const confirmDelete = useCallback(async () => {
|
||||||
setAssets((prev) => {
|
if (!pendingDeleteId) return;
|
||||||
const found = prev.find((a) => a.id === id);
|
const target = assets.find((a) => a.id === pendingDeleteId);
|
||||||
if (found?.previewUrl) URL.revokeObjectURL(found.previewUrl);
|
if (!target) {
|
||||||
return prev.filter((a) => a.id !== id);
|
setPendingDeleteId(null);
|
||||||
});
|
return;
|
||||||
}, []);
|
}
|
||||||
|
|
||||||
|
setIsDeleting(true);
|
||||||
|
|
||||||
|
if (target.file && target.previewUrl) URL.revokeObjectURL(target.previewUrl);
|
||||||
|
|
||||||
|
if (analysisRunId && target.remoteId != null) {
|
||||||
|
try {
|
||||||
|
await deleteFile({ runId: analysisRunId, fileId: target.remoteId });
|
||||||
|
filesQuery.refetch();
|
||||||
|
} catch {
|
||||||
|
setIsDeleting(false);
|
||||||
|
setPendingDeleteId(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAssets((prev) => prev.filter((a) => a.id !== pendingDeleteId));
|
||||||
|
setIsDeleting(false);
|
||||||
|
setPendingDeleteId(null);
|
||||||
|
}, [pendingDeleteId, assets, analysisRunId, deleteFile, filesQuery]);
|
||||||
|
|
||||||
|
const pendingDeleteAsset = pendingDeleteId
|
||||||
|
? assets.find((a) => a.id === pendingDeleteId) ?? null
|
||||||
|
: null;
|
||||||
|
|
||||||
const filtered =
|
const filtered =
|
||||||
activeFilter === 'all' ? assets : assets.filter((a) => a.category === activeFilter);
|
activeFilter === 'all' ? assets : assets.filter((a) => a.category === activeFilter);
|
||||||
|
|
||||||
const counts = {
|
const counts: Record<UploadCategory, number> = {
|
||||||
all: assets.length,
|
all: assets.length,
|
||||||
image: assets.filter((a) => a.category === 'image').length,
|
image: assets.filter((a) => a.category === 'image').length,
|
||||||
video: assets.filter((a) => a.category === 'video').length,
|
video: assets.filter((a) => a.category === 'video').length,
|
||||||
text: assets.filter((a) => a.category === 'text').length,
|
audio: assets.filter((a) => a.category === 'audio').length,
|
||||||
|
document: assets.filter((a) => a.category === 'document').length,
|
||||||
|
file: assets.filter((a) => a.category === 'file').length,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -143,17 +283,17 @@ export default function MyAssetUpload() {
|
||||||
파일을 드래그하거나 클릭하여 업로드
|
파일을 드래그하거나 클릭하여 업로드
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-slate-400">
|
<p className="text-sm text-slate-400">
|
||||||
Image, Video, Text 파일 지원 (JPG, PNG, MP4, MOV, TXT, PDF, DOC 등)
|
Image, Video, Audio, Document, File 지원 (JPG, MP4, MP3, PDF, DOCX, ZIP 등)
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* File Type Badges */}
|
{/* File Type Badges */}
|
||||||
<div className="flex justify-center gap-2 mt-4">
|
<div className="flex flex-wrap justify-center gap-2 mt-4">
|
||||||
{(['image', 'video', 'text'] as const).map((cat) => (
|
{(['image', 'video', 'audio', 'document', 'file'] as const).map((cat) => (
|
||||||
<span
|
<span
|
||||||
key={cat}
|
key={cat}
|
||||||
className={`rounded-full px-3 py-1 text-xs font-medium ${categoryBadge[cat]}`}
|
className={`rounded-full px-3 py-1 text-xs font-medium ${categoryBadge[cat]}`}
|
||||||
>
|
>
|
||||||
{cat === 'image' ? 'Image' : cat === 'video' ? 'Video' : 'Text'}
|
{categoryConfig[cat].label}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -216,7 +356,9 @@ export default function MyAssetUpload() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{asset.category === 'text' && (
|
{(asset.category === 'audio' ||
|
||||||
|
asset.category === 'document' ||
|
||||||
|
asset.category === 'file') && (
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<FileTextFilled size={36} className="text-[#D4A872]" />
|
<FileTextFilled size={36} className="text-[#D4A872]" />
|
||||||
<span className="text-xs text-slate-400 font-medium">
|
<span className="text-xs text-slate-400 font-medium">
|
||||||
|
|
@ -230,7 +372,7 @@ export default function MyAssetUpload() {
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
onClick={() => removeAsset(asset.id)}
|
onClick={() => setPendingDeleteId(asset.id)}
|
||||||
className="absolute top-2 right-2 w-7 h-7 size-7 rounded-full bg-white/90 backdrop-blur-sm border border-slate-100 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-sm hover:bg-brand-rose-bg"
|
className="absolute top-2 right-2 w-7 h-7 size-7 rounded-full bg-white/90 backdrop-blur-sm border border-slate-100 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-sm hover:bg-brand-rose-bg"
|
||||||
>
|
>
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||||
|
|
@ -242,11 +384,7 @@ export default function MyAssetUpload() {
|
||||||
<span
|
<span
|
||||||
className={`absolute top-2 left-2 rounded-full px-3 py-1 text-xs font-semibold ${categoryBadge[asset.category]}`}
|
className={`absolute top-2 left-2 rounded-full px-3 py-1 text-xs font-semibold ${categoryBadge[asset.category]}`}
|
||||||
>
|
>
|
||||||
{asset.category === 'image'
|
{categoryConfig[asset.category].label}
|
||||||
? 'Image'
|
|
||||||
: asset.category === 'video'
|
|
||||||
? 'Video'
|
|
||||||
: 'Text'}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Video Duration Overlay */}
|
{/* Video Duration Overlay */}
|
||||||
|
|
@ -263,7 +401,28 @@ export default function MyAssetUpload() {
|
||||||
<p className="text-sm font-medium text-brand-navy truncate mb-1">
|
<p className="text-sm font-medium text-brand-navy truncate mb-1">
|
||||||
{asset.name}
|
{asset.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-slate-400">{asset.size}</p>
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<p className="text-xs text-slate-400">
|
||||||
|
{asset.size || categoryConfig[asset.category].label}
|
||||||
|
</p>
|
||||||
|
{asset.status === 'uploading' && (
|
||||||
|
<span className="inline-flex items-center gap-1 text-xs text-slate-500">
|
||||||
|
<Spinner size="xs" />
|
||||||
|
업로드 중
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{asset.status === 'done' && (
|
||||||
|
<span className="text-xs text-emerald-600">완료</span>
|
||||||
|
)}
|
||||||
|
{asset.status === 'error' && (
|
||||||
|
<span
|
||||||
|
className="text-xs text-[#7C3A4B] truncate"
|
||||||
|
title={asset.errorMessage}
|
||||||
|
>
|
||||||
|
업로드 실패
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -271,6 +430,43 @@ export default function MyAssetUpload() {
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={pendingDeleteId !== null}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isDeleting) setPendingDeleteId(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>파일을 삭제할까요?</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{pendingDeleteAsset
|
||||||
|
? `"${pendingDeleteAsset.name}" 을(를) 삭제합니다. 이 동작은 되돌릴 수 없습니다.`
|
||||||
|
: '이 동작은 되돌릴 수 없습니다.'}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="gap-2 sm:gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setPendingDeleteId(null)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
취소
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={confirmDelete}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="bg-[#7C3A4B] hover:bg-[#7C3A4B]/90 text-white"
|
||||||
|
>
|
||||||
|
{isDeleting ? <Spinner size="xs" className="mr-1" /> : null}
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,54 @@
|
||||||
export type UploadCategory = 'all' | 'image' | 'video' | 'text';
|
/**
|
||||||
|
* 백엔드 FileType 과 1:1 매칭되는 5종 분류.
|
||||||
|
* image — jpg, png, gif, webp, heic
|
||||||
|
* video — mp4, mov, avi, webm
|
||||||
|
* audio — mp3, wav, m4a, ogg
|
||||||
|
* document — pdf, docx, xlsx, pptx, txt, csv, hwp
|
||||||
|
* file — 그 외/분류 애매 (zip, json 등)
|
||||||
|
*/
|
||||||
|
export type AssetCategory = 'image' | 'video' | 'audio' | 'document' | 'file';
|
||||||
|
export type UploadCategory = 'all' | AssetCategory;
|
||||||
|
|
||||||
export const categoryConfig: Record<UploadCategory, { label: string }> = {
|
export const categoryConfig: Record<UploadCategory, { label: string }> = {
|
||||||
all: { label: '전체' },
|
all: { label: '전체' },
|
||||||
image: { label: 'Image' },
|
image: { label: 'Image' },
|
||||||
video: { label: 'Video' },
|
video: { label: 'Video' },
|
||||||
text: { label: 'Text' },
|
audio: { label: 'Audio' },
|
||||||
|
document: { label: 'Document' },
|
||||||
|
file: { label: 'File' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const categoryBadge: Record<'image' | 'video' | 'text', string> = {
|
export const categoryBadge: Record<AssetCategory, string> = {
|
||||||
image: 'bg-brand-tint-purple text-brand-purple-muted shadow-[2px_3px_6px_rgba(155,138,212,0.12)]',
|
image: 'bg-brand-tint-purple text-brand-purple-muted shadow-[2px_3px_6px_rgba(155,138,212,0.12)]',
|
||||||
video: 'bg-brand-rose-bg text-brand-rose shadow-[2px_3px_6px_rgba(212,136,154,0.12)]',
|
video: 'bg-brand-rose-bg text-brand-rose shadow-[2px_3px_6px_rgba(212,136,154,0.12)]',
|
||||||
text: 'bg-brand-earth-bg text-brand-earth shadow-[2px_3px_6px_rgba(212,168,114,0.12)]',
|
audio: 'bg-sky-50 text-sky-700 shadow-[2px_3px_6px_rgba(56,189,248,0.12)]',
|
||||||
|
document: 'bg-brand-earth-bg text-brand-earth shadow-[2px_3px_6px_rgba(212,168,114,0.12)]',
|
||||||
|
file: 'bg-slate-100 text-slate-600 shadow-[2px_3px_6px_rgba(100,116,139,0.10)]',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ACCEPT_MAP: Record<string, string> = {
|
// 확장자 → 카테고리. 백엔드 분류 규칙과 동일.
|
||||||
'image/*': '.jpg,.jpeg,.png,.gif,.webp,.svg',
|
const EXT_BY_CATEGORY: Record<AssetCategory, string[]> = {
|
||||||
'video/*': '.mp4,.mov,.webm,.avi',
|
image: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic'],
|
||||||
'text/*': '.txt,.md,.doc,.docx,.pdf,.csv,.json',
|
video: ['mp4', 'mov', 'avi', 'webm'],
|
||||||
|
audio: ['mp3', 'wav', 'm4a', 'ogg'],
|
||||||
|
document: ['pdf', 'docx', 'xlsx', 'pptx', 'txt', 'csv', 'hwp'],
|
||||||
|
file: [], // 폴백
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ALL_ACCEPT = Object.values(ACCEPT_MAP).join(',');
|
export const ALL_ACCEPT = (Object.entries(EXT_BY_CATEGORY) as [AssetCategory, string[]][])
|
||||||
|
.flatMap(([, exts]) => exts.map((e) => `.${e}`))
|
||||||
|
.join(',');
|
||||||
|
|
||||||
export function categorize(file: File): 'image' | 'video' | 'text' {
|
export function categorize(file: File): AssetCategory {
|
||||||
|
const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
|
||||||
|
for (const [cat, exts] of Object.entries(EXT_BY_CATEGORY) as [AssetCategory, string[]][]) {
|
||||||
|
if (exts.includes(ext)) return cat;
|
||||||
|
}
|
||||||
|
// MIME 폴백 — 확장자 누락된 케이스 보완.
|
||||||
if (file.type.startsWith('image/')) return 'image';
|
if (file.type.startsWith('image/')) return 'image';
|
||||||
if (file.type.startsWith('video/')) return 'video';
|
if (file.type.startsWith('video/')) return 'video';
|
||||||
return 'text';
|
if (file.type.startsWith('audio/')) return 'audio';
|
||||||
|
return 'file';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatSize(bytes: number): string {
|
export function formatSize(bytes: number): string {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import type { MarketingPlan } from '@/features/plan/types/plan';
|
import type { MarketingPlan } from '@/features/plan/types/plan';
|
||||||
import { getPlan } from '@/shared/api/generated/plans/plans';
|
import { getPlan } from '@/shared/api/generated/plan/plan';
|
||||||
|
|
||||||
interface UseMarketingPlanResult {
|
interface UseMarketingPlanResult {
|
||||||
data: MarketingPlan | null;
|
data: MarketingPlan | null;
|
||||||
|
|
@ -32,19 +32,14 @@ export function useMarketingPlan(id: string | undefined): UseMarketingPlanResult
|
||||||
throw new Error('마케팅 기획이 아직 생성되지 않았습니다.');
|
throw new Error('마케팅 기획이 아직 생성되지 않았습니다.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDK PlanApiResponse 가 MarketingPlan 과 사실상 동일 — 패스스루.
|
||||||
|
// reportId/workflow 는 SDK 에 없는 로컬 전용 필드.
|
||||||
setData({
|
setData({
|
||||||
id: id!,
|
...(planOutput as unknown as MarketingPlan),
|
||||||
|
id: planOutput.id || id!,
|
||||||
reportId: id!,
|
reportId: id!,
|
||||||
clinicName: '',
|
clinicName: planOutput.clinicName ?? '',
|
||||||
clinicNameEn: '',
|
clinicNameEn: planOutput.clinicNameEn ?? '',
|
||||||
createdAt: '',
|
|
||||||
targetUrl: '',
|
|
||||||
brandGuide: planOutput.brandGuide as MarketingPlan['brandGuide'],
|
|
||||||
channelStrategies: planOutput.channelStrategies as MarketingPlan['channelStrategies'],
|
|
||||||
contentStrategy: planOutput.contentStrategy as MarketingPlan['contentStrategy'],
|
|
||||||
calendar: planOutput.calendar as MarketingPlan['calendar'],
|
|
||||||
assetCollection: planOutput.assetCollection as MarketingPlan['assetCollection'],
|
|
||||||
repurposingProposals: (planOutput.repurposingProposals ?? undefined) as MarketingPlan['repurposingProposals'],
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch marketing plan');
|
setError(err instanceof Error ? err.message : 'Failed to fetch marketing plan');
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/**
|
|
||||||
* GuestPlanPage — `/plan/:id`
|
|
||||||
*
|
|
||||||
* 손님(비계약 방문자)이 보는 플랜 미리보기. 본문은 UserPlanPage 와 동일하나,
|
|
||||||
* 인터랙티브 섹션(자산 업로드/전략 조정/워크플로우)은 노출되지 않고
|
|
||||||
* 하단에 도입 문의 CTA(PlanCTA) 가 붙습니다.
|
|
||||||
*/
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useParams, useLocation } from 'react-router';
|
|
||||||
import { useMarketingPlan } from '../hooks/useMarketingPlan';
|
|
||||||
import { ReportNav } from '@/features/report/components/ReportNav';
|
|
||||||
import { PLAN_SECTIONS } from '@/shared/constants/planSections';
|
|
||||||
import PlanBody from '../components/PlanBody';
|
|
||||||
|
|
||||||
export default function GuestPlanPage() {
|
|
||||||
const { id } = useParams<{ id: string }>();
|
|
||||||
const location = useLocation();
|
|
||||||
const { data, isLoading, error } = useMarketingPlan(id);
|
|
||||||
|
|
||||||
// 해시 기반 스크롤: /plan/:id#section-id → 렌더링 후 해당 섹션으로
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading || !location.hash) return;
|
|
||||||
const sectionId = location.hash.slice(1);
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
const el = document.getElementById(sectionId);
|
|
||||||
if (!el) return;
|
|
||||||
const STICKY_OFFSET = 128;
|
|
||||||
const y = el.getBoundingClientRect().top + window.scrollY - STICKY_OFFSET;
|
|
||||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
||||||
}, 300);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [isLoading, location.hash]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen flex items-center justify-center pt-20">
|
|
||||||
<div className="flex flex-col items-center gap-4">
|
|
||||||
<div className="w-10 h-10 border-4 border-[#6C5CE7] border-t-transparent rounded-full animate-spin" />
|
|
||||||
<p className="text-slate-500 text-sm">마케팅 기획을 불러오는 중...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !data) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen flex items-center justify-center pt-20">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-[#7C3A4B] font-medium mb-2">오류가 발생했습니다</p>
|
|
||||||
<p className="text-slate-500 text-sm">
|
|
||||||
{error ?? '마케팅 기획을 찾을 수 없습니다.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pt-20">
|
|
||||||
<ReportNav sections={PLAN_SECTIONS} />
|
|
||||||
<PlanBody data={data} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* UserPlanPage — `/clinics/:clinicId/plan/:id`
|
* PlanPage — 마케팅 기획 화면 (단일 페이지).
|
||||||
*
|
*
|
||||||
* 계약된 병원 유저가 워크스페이스에서 운영하는 마케팅 기획 화면.
|
* 두 경로에서 동일 컴포넌트로 진입:
|
||||||
* GuestPlanPage 의 본문 + 워크스페이스 액션바 + 인터랙티브 섹션
|
* - `/plan/:id` (랜딩 → 분석 → 리포트 → 플랜)
|
||||||
* (MyAssetUpload / WorkflowTracker).
|
* - `/clinics/:clinicId/plan/:id` (워크스페이스 진입)
|
||||||
*
|
*
|
||||||
* NOTE: StrategyAdjustmentSection(성과 기반 전략 조정) 은 성과 데이터 파이프라인 의존 →
|
* 본문(PlanBody) + 인터랙티브 섹션(MyAssetUpload / WorkflowTracker) + 도입 문의 CTA 모두 노출.
|
||||||
* 본 차수 미포함, 후속 모듈로 이관. 아래 import/render 주석 처리.
|
* 업로드는 analysisRunId(=URL :id) 를 항상 넘기므로 어디서 들어와도 SDK 호출.
|
||||||
*/
|
*/
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams, useLocation } from 'react-router';
|
import { useParams, useLocation } from 'react-router';
|
||||||
|
|
@ -15,15 +15,15 @@ import { ReportNav } from '@/features/report/components/ReportNav';
|
||||||
import { PLAN_SECTIONS } from '@/shared/constants/planSections';
|
import { PLAN_SECTIONS } from '@/shared/constants/planSections';
|
||||||
import PlanBody from '../components/PlanBody';
|
import PlanBody from '../components/PlanBody';
|
||||||
import MyAssetUpload from '../components/MyAssetUpload';
|
import MyAssetUpload from '../components/MyAssetUpload';
|
||||||
// import StrategyAdjustmentSection from '../components/StrategyAdjustmentSection';
|
|
||||||
import WorkflowTracker from '../components/WorkflowTracker';
|
import WorkflowTracker from '../components/WorkflowTracker';
|
||||||
|
import PlanCTA from '../components/PlanCTA';
|
||||||
|
|
||||||
export default function UserPlanPage() {
|
export default function PlanPage() {
|
||||||
const { id } = useParams<{ clinicId: string; id: string }>();
|
const { id } = useParams<{ clinicId?: string; id: string }>();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
// const stateClinicId = (location.state as { clinicId?: string } | undefined)?.clinicId || null;
|
|
||||||
const { data, isLoading, error } = useMarketingPlan(id);
|
const { data, isLoading, error } = useMarketingPlan(id);
|
||||||
|
|
||||||
|
// 해시 기반 스크롤: /plan/:id#section-id → 렌더링 후 해당 섹션으로
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoading || !location.hash) return;
|
if (isLoading || !location.hash) return;
|
||||||
const sectionId = location.hash.slice(1);
|
const sectionId = location.hash.slice(1);
|
||||||
|
|
@ -65,7 +65,6 @@ export default function UserPlanPage() {
|
||||||
|
|
||||||
<PlanBody data={data} />
|
<PlanBody data={data} />
|
||||||
|
|
||||||
{/* 유저 전용 인터랙티브 섹션 */}
|
|
||||||
{data.workflow && (
|
{data.workflow && (
|
||||||
<div data-no-print>
|
<div data-no-print>
|
||||||
<WorkflowTracker data={data.workflow} />
|
<WorkflowTracker data={data.workflow} />
|
||||||
|
|
@ -73,13 +72,10 @@ export default function UserPlanPage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div data-no-print>
|
<div data-no-print>
|
||||||
<MyAssetUpload />
|
<MyAssetUpload analysisRunId={id} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 성과 기반 전략 조정 — 본 차수 미포함, 후속 모듈 이관 */}
|
<PlanCTA />
|
||||||
{/* <div data-no-print>
|
|
||||||
<StrategyAdjustmentSection clinicId={clinicId ?? stateClinicId} planId={data.id} />
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { lazy } from 'react'
|
import { lazy } from 'react'
|
||||||
import type { RouteObject } from 'react-router'
|
import type { RouteObject } from 'react-router'
|
||||||
|
|
||||||
const GuestPlanPage = lazy(() => import('./pages/GuestPlanPage'))
|
const PlanPage = lazy(() => import('./pages/PlanPage'))
|
||||||
|
|
||||||
export const planRoutes: RouteObject[] = [
|
export const planRoutes: RouteObject[] = [
|
||||||
// 손님(랜딩→분석→리포트→플랜) 흐름. 유저 워크스페이스 경로는 features/clinics/routes.tsx 참조.
|
// `/plan/:id` 와 `/clinics/:clinicId/plan/:id` 모두 동일 PlanPage 사용.
|
||||||
{ path: 'plan/:id', element: <GuestPlanPage /> },
|
// 워크스페이스 경로 정의는 features/clinics/routes.tsx 참조.
|
||||||
|
{ path: 'plan/:id', element: <PlanPage /> },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -127,9 +127,8 @@ export default function ClinicSnapshot({ data }: ClinicSnapshotProps) {
|
||||||
{data.certifications.map((cert) => (
|
{data.certifications.map((cert) => (
|
||||||
<span
|
<span
|
||||||
key={cert}
|
key={cert}
|
||||||
className="inline-flex items-center gap-1.5 rounded-full bg-brand-tint-purple border border-brand-tint-lavender px-3 py-1 text-sm font-medium text-brand-purple-muted"
|
className="rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700"
|
||||||
>
|
>
|
||||||
<BadgeCheck size={12} className="text-brand-purple" />
|
|
||||||
{cert}
|
{cert}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import type { MarketingReport } from '@/features/report/types/report';
|
import type { MarketingReport } from '@/features/report/types/report';
|
||||||
import { getReport } from '@/shared/api/generated/reports/reports';
|
import { getReport } from '@/shared/api/generated/report/report';
|
||||||
import { transformReportOutput } from '@/features/report/lib/transformReport';
|
|
||||||
|
|
||||||
interface UseReportResult {
|
interface UseReportResult {
|
||||||
data: MarketingReport | null;
|
data: MarketingReport | null;
|
||||||
|
|
@ -33,8 +32,9 @@ export function useReport(id: string | undefined): UseReportResult {
|
||||||
if (!output) {
|
if (!output) {
|
||||||
throw new Error('리포트 데이터가 비어있습니다.');
|
throw new Error('리포트 데이터가 비어있습니다.');
|
||||||
}
|
}
|
||||||
const transformed = transformReportOutput(id, output, { url: '', generatedAt: '' });
|
// SDK MarketingReportResponse 가 사실상 MarketingReport 와 같은 shape — 그대로 사용.
|
||||||
setData(transformed);
|
// 미세한 차이(ScreenshotEvidence 등)는 컴포넌트 단에서 옵셔널로 처리됨.
|
||||||
|
setData(output as unknown as MarketingReport);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch report');
|
setError(err instanceof Error ? err.message : 'Failed to fetch report');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import type { MarketingReport, Severity, ChannelScore, DiagnosisItem, TopVideo } from '@/features/report/types/report';
|
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 응답.
|
* generate-report Edge Function의 API 응답.
|
||||||
|
|
@ -1269,203 +1267,5 @@ export function mergeEnrichment(
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════════════
|
// SDK MarketingReportResponse ≈ MarketingReport — 별도 변환 함수 없이 useReport 에서
|
||||||
// SDK ReportOutput → MarketingReport 변환
|
// 직접 캐스팅해 사용. (구버전 ReportOutput 매핑 코드 제거)
|
||||||
// 백엔드 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: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ import type {
|
||||||
AnalysisCreate,
|
AnalysisCreate,
|
||||||
AnalysisStartResponse,
|
AnalysisStartResponse,
|
||||||
AnalysisStatusResponse,
|
AnalysisStatusResponse,
|
||||||
|
BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost,
|
||||||
|
FileListItem,
|
||||||
|
FileUploadResponse,
|
||||||
HTTPValidationError
|
HTTPValidationError
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
|
|
@ -128,6 +131,312 @@ export const useStartAnalysis = <TError = HTTPValidationError,
|
||||||
return useMutation(mutationOptions, queryClient);
|
return useMutation(mutationOptions, queryClient);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
* @summary Upload Analysis Run File
|
||||||
|
*/
|
||||||
|
export type uploadAnalysisRunFileResponse201 = {
|
||||||
|
data: FileUploadResponse
|
||||||
|
status: 201
|
||||||
|
}
|
||||||
|
|
||||||
|
export type uploadAnalysisRunFileResponse422 = {
|
||||||
|
data: HTTPValidationError
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
|
||||||
|
export type uploadAnalysisRunFileResponseSuccess = (uploadAnalysisRunFileResponse201) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type uploadAnalysisRunFileResponseError = (uploadAnalysisRunFileResponse422) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type uploadAnalysisRunFileResponse = (uploadAnalysisRunFileResponseSuccess | uploadAnalysisRunFileResponseError)
|
||||||
|
|
||||||
|
export const getUploadAnalysisRunFileUrl = (runId: string,) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return `/api/analysis/${runId}/files`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const uploadAnalysisRunFile = async (runId: string,
|
||||||
|
bodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost, options?: RequestInit): Promise<uploadAnalysisRunFileResponse> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append(`file`, bodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost.file)
|
||||||
|
if(bodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost.file_type !== undefined) {
|
||||||
|
formData.append(`file_type`, bodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost.file_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customFetcher<uploadAnalysisRunFileResponse>(getUploadAnalysisRunFileUrl(runId),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'POST'
|
||||||
|
,
|
||||||
|
body:
|
||||||
|
formData,
|
||||||
|
}
|
||||||
|
);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getUploadAnalysisRunFileMutationOptions = <TError = HTTPValidationError,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof uploadAnalysisRunFile>>, TError,{runId: string;data: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost}, TContext>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
): UseMutationOptions<Awaited<ReturnType<typeof uploadAnalysisRunFile>>, TError,{runId: string;data: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost}, TContext> => {
|
||||||
|
|
||||||
|
const mutationKey = ['uploadAnalysisRunFile'];
|
||||||
|
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<Awaited<ReturnType<typeof uploadAnalysisRunFile>>, {runId: string;data: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost}> = (props) => {
|
||||||
|
const {runId,data} = props ?? {};
|
||||||
|
|
||||||
|
return uploadAnalysisRunFile(runId,data,requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions }}
|
||||||
|
|
||||||
|
export type UploadAnalysisRunFileMutationResult = NonNullable<Awaited<ReturnType<typeof uploadAnalysisRunFile>>>
|
||||||
|
export type UploadAnalysisRunFileMutationBody = BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost
|
||||||
|
export type UploadAnalysisRunFileMutationError = HTTPValidationError
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Upload Analysis Run File
|
||||||
|
*/
|
||||||
|
export const useUploadAnalysisRunFile = <TError = HTTPValidationError,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof uploadAnalysisRunFile>>, TError,{runId: string;data: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost}, TContext>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof uploadAnalysisRunFile>>,
|
||||||
|
TError,
|
||||||
|
{runId: string;data: BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost},
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
|
||||||
|
const mutationOptions = getUploadAnalysisRunFileMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions, queryClient);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @summary Get Analysis Run Files
|
||||||
|
*/
|
||||||
|
export type getAnalysisRunFilesResponse200 = {
|
||||||
|
data: FileListItem[]
|
||||||
|
status: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
export type getAnalysisRunFilesResponse422 = {
|
||||||
|
data: HTTPValidationError
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
|
||||||
|
export type getAnalysisRunFilesResponseSuccess = (getAnalysisRunFilesResponse200) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type getAnalysisRunFilesResponseError = (getAnalysisRunFilesResponse422) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type getAnalysisRunFilesResponse = (getAnalysisRunFilesResponseSuccess | getAnalysisRunFilesResponseError)
|
||||||
|
|
||||||
|
export const getGetAnalysisRunFilesUrl = (runId: string,) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return `/api/analysis/${runId}/files`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAnalysisRunFiles = async (runId: string, options?: RequestInit): Promise<getAnalysisRunFilesResponse> => {
|
||||||
|
|
||||||
|
return customFetcher<getAnalysisRunFilesResponse>(getGetAnalysisRunFilesUrl(runId),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'GET'
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getGetAnalysisRunFilesQueryKey = (runId?: string,) => {
|
||||||
|
return [
|
||||||
|
`/api/analysis/${runId}/files`
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getGetAnalysisRunFilesQueryOptions = <TData = Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError = HTTPValidationError>(runId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getGetAnalysisRunFilesQueryKey(runId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof getAnalysisRunFiles>>> = () => getAnalysisRunFiles(runId, requestOptions);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { queryKey, queryFn, enabled: !!(runId), staleTime: 60000, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetAnalysisRunFilesQueryResult = NonNullable<Awaited<ReturnType<typeof getAnalysisRunFiles>>>
|
||||||
|
export type GetAnalysisRunFilesQueryError = HTTPValidationError
|
||||||
|
|
||||||
|
|
||||||
|
export function useGetAnalysisRunFiles<TData = Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError = HTTPValidationError>(
|
||||||
|
runId: string, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData>> & Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof getAnalysisRunFiles>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof getAnalysisRunFiles>>
|
||||||
|
> , 'initialData'
|
||||||
|
>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
export function useGetAnalysisRunFiles<TData = Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError = HTTPValidationError>(
|
||||||
|
runId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData>> & Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof getAnalysisRunFiles>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof getAnalysisRunFiles>>
|
||||||
|
> , 'initialData'
|
||||||
|
>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
export function useGetAnalysisRunFiles<TData = Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError = HTTPValidationError>(
|
||||||
|
runId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
/**
|
||||||
|
* @summary Get Analysis Run Files
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetAnalysisRunFiles<TData = Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError = HTTPValidationError>(
|
||||||
|
runId: string, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getAnalysisRunFiles>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> } {
|
||||||
|
|
||||||
|
const queryOptions = getGetAnalysisRunFilesQueryOptions(runId,options)
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> };
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey ;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Delete Analysis Run File
|
||||||
|
*/
|
||||||
|
export type deleteAnalysisRunFileResponse204 = {
|
||||||
|
data: void
|
||||||
|
status: 204
|
||||||
|
}
|
||||||
|
|
||||||
|
export type deleteAnalysisRunFileResponse422 = {
|
||||||
|
data: HTTPValidationError
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
|
||||||
|
export type deleteAnalysisRunFileResponseSuccess = (deleteAnalysisRunFileResponse204) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type deleteAnalysisRunFileResponseError = (deleteAnalysisRunFileResponse422) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type deleteAnalysisRunFileResponse = (deleteAnalysisRunFileResponseSuccess | deleteAnalysisRunFileResponseError)
|
||||||
|
|
||||||
|
export const getDeleteAnalysisRunFileUrl = (runId: string,
|
||||||
|
fileId: number,) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return `/api/analysis/${runId}/files/${fileId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteAnalysisRunFile = async (runId: string,
|
||||||
|
fileId: number, options?: RequestInit): Promise<deleteAnalysisRunFileResponse> => {
|
||||||
|
|
||||||
|
return customFetcher<deleteAnalysisRunFileResponse>(getDeleteAnalysisRunFileUrl(runId,fileId),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'DELETE'
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getDeleteAnalysisRunFileMutationOptions = <TError = HTTPValidationError,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof deleteAnalysisRunFile>>, TError,{runId: string;fileId: number}, TContext>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
): UseMutationOptions<Awaited<ReturnType<typeof deleteAnalysisRunFile>>, TError,{runId: string;fileId: number}, TContext> => {
|
||||||
|
|
||||||
|
const mutationKey = ['deleteAnalysisRunFile'];
|
||||||
|
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<Awaited<ReturnType<typeof deleteAnalysisRunFile>>, {runId: string;fileId: number}> = (props) => {
|
||||||
|
const {runId,fileId} = props ?? {};
|
||||||
|
|
||||||
|
return deleteAnalysisRunFile(runId,fileId,requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions }}
|
||||||
|
|
||||||
|
export type DeleteAnalysisRunFileMutationResult = NonNullable<Awaited<ReturnType<typeof deleteAnalysisRunFile>>>
|
||||||
|
|
||||||
|
export type DeleteAnalysisRunFileMutationError = HTTPValidationError
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Delete Analysis Run File
|
||||||
|
*/
|
||||||
|
export const useDeleteAnalysisRunFile = <TError = HTTPValidationError,
|
||||||
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof deleteAnalysisRunFile>>, TError,{runId: string;fileId: number}, TContext>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof deleteAnalysisRunFile>>,
|
||||||
|
TError,
|
||||||
|
{runId: string;fileId: number},
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
|
||||||
|
const mutationOptions = getDeleteAnalysisRunFileMutationOptions(options);
|
||||||
|
|
||||||
|
return useMutation(mutationOptions, queryClient);
|
||||||
|
}
|
||||||
|
/**
|
||||||
* @summary Get Analysis Status
|
* @summary Get Analysis Status
|
||||||
*/
|
*/
|
||||||
export type getAnalysisStatusResponse200 = {
|
export type getAnalysisStatusResponse200 = {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ import type {
|
||||||
ClinicCreate,
|
ClinicCreate,
|
||||||
ClinicCreateResponse,
|
ClinicCreateResponse,
|
||||||
ClinicHistoryResponse,
|
ClinicHistoryResponse,
|
||||||
|
ClinicListResponse,
|
||||||
ClinicResponse,
|
ClinicResponse,
|
||||||
|
GetClinicsParams,
|
||||||
HTTPValidationError
|
HTTPValidationError
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
|
|
@ -38,6 +40,132 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get Clinics
|
||||||
|
*/
|
||||||
|
export type getClinicsResponse200 = {
|
||||||
|
data: ClinicListResponse
|
||||||
|
status: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
export type getClinicsResponse422 = {
|
||||||
|
data: HTTPValidationError
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
|
||||||
|
export type getClinicsResponseSuccess = (getClinicsResponse200) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type getClinicsResponseError = (getClinicsResponse422) & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type getClinicsResponse = (getClinicsResponseSuccess | getClinicsResponseError)
|
||||||
|
|
||||||
|
export const getGetClinicsUrl = (params?: GetClinicsParams,) => {
|
||||||
|
const normalizedParams = new URLSearchParams();
|
||||||
|
|
||||||
|
Object.entries(params || {}).forEach(([key, value]) => {
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
normalizedParams.append(key, value === null ? 'null' : value.toString())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringifiedParams = normalizedParams.toString();
|
||||||
|
|
||||||
|
return stringifiedParams.length > 0 ? `/api/clinics?${stringifiedParams}` : `/api/clinics`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getClinics = async (params?: GetClinicsParams, options?: RequestInit): Promise<getClinicsResponse> => {
|
||||||
|
|
||||||
|
return customFetcher<getClinicsResponse>(getGetClinicsUrl(params),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: 'GET'
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
);}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getGetClinicsQueryKey = (params?: GetClinicsParams,) => {
|
||||||
|
return [
|
||||||
|
`/api/clinics`, ...(params ? [params]: [])
|
||||||
|
] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getGetClinicsQueryOptions = <TData = Awaited<ReturnType<typeof getClinics>>, TError = HTTPValidationError>(params?: GetClinicsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
) => {
|
||||||
|
|
||||||
|
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||||
|
|
||||||
|
const queryKey = queryOptions?.queryKey ?? getGetClinicsQueryKey(params);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof getClinics>>> = () => getClinics(params, requestOptions);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { queryKey, queryFn, staleTime: 60000, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetClinicsQueryResult = NonNullable<Awaited<ReturnType<typeof getClinics>>>
|
||||||
|
export type GetClinicsQueryError = HTTPValidationError
|
||||||
|
|
||||||
|
|
||||||
|
export function useGetClinics<TData = Awaited<ReturnType<typeof getClinics>>, TError = HTTPValidationError>(
|
||||||
|
params: undefined | GetClinicsParams, options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData>> & Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof getClinics>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof getClinics>>
|
||||||
|
> , 'initialData'
|
||||||
|
>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
export function useGetClinics<TData = Awaited<ReturnType<typeof getClinics>>, TError = HTTPValidationError>(
|
||||||
|
params?: GetClinicsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData>> & Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof getClinics>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof getClinics>>
|
||||||
|
> , 'initialData'
|
||||||
|
>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
export function useGetClinics<TData = Awaited<ReturnType<typeof getClinics>>, TError = HTTPValidationError>(
|
||||||
|
params?: GetClinicsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> }
|
||||||
|
/**
|
||||||
|
* @summary Get Clinics
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useGetClinics<TData = Awaited<ReturnType<typeof getClinics>>, TError = HTTPValidationError>(
|
||||||
|
params?: GetClinicsParams, options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof getClinics>>, TError, TData>>, request?: SecondParameter<typeof customFetcher>}
|
||||||
|
, queryClient?: QueryClient
|
||||||
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> } {
|
||||||
|
|
||||||
|
const queryOptions = getGetClinicsQueryOptions(params,options)
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData> };
|
||||||
|
|
||||||
|
query.queryKey = queryOptions.queryKey ;
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Create Clinic
|
* @summary Create Clinic
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import type {
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
GetPlan200,
|
HTTPValidationError,
|
||||||
HTTPValidationError
|
PlanApiResponse
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
import { customFetcher } from '../../api';
|
import { customFetcher } from '../../api';
|
||||||
|
|
@ -35,7 +35,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
* @summary Get Plan
|
* @summary Get Plan
|
||||||
*/
|
*/
|
||||||
export type getPlanResponse200 = {
|
export type getPlanResponse200 = {
|
||||||
data: GetPlan200
|
data: PlanApiResponse
|
||||||
status: 200
|
status: 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ export const getGetPlanUrl = (runId: string,) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return `/api/plans/${runId}`
|
return `/api/plan/${runId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPlan = async (runId: string, options?: RequestInit): Promise<getPlanResponse> => {
|
export const getPlan = async (runId: string, options?: RequestInit): Promise<getPlanResponse> => {
|
||||||
|
|
@ -78,7 +78,7 @@ export const getPlan = async (runId: string, options?: RequestInit): Promise<get
|
||||||
|
|
||||||
export const getGetPlanQueryKey = (runId?: string,) => {
|
export const getGetPlanQueryKey = (runId?: string,) => {
|
||||||
return [
|
return [
|
||||||
`/api/plans/${runId}`
|
`/api/plan/${runId}`
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,8 +20,8 @@ import type {
|
||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
GetReport200,
|
HTTPValidationError,
|
||||||
HTTPValidationError
|
MarketingReportResponse
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
import { customFetcher } from '../../api';
|
import { customFetcher } from '../../api';
|
||||||
|
|
@ -35,7 +35,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||||
* @summary Get Report
|
* @summary Get Report
|
||||||
*/
|
*/
|
||||||
export type getReportResponse200 = {
|
export type getReportResponse200 = {
|
||||||
data: GetReport200
|
data: MarketingReportResponse
|
||||||
status: 200
|
status: 200
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ export const getGetReportUrl = (runId: string,) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return `/api/reports/${runId}`
|
return `/api/report/${runId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getReport = async (runId: string, options?: RequestInit): Promise<getReportResponse> => {
|
export const getReport = async (runId: string, options?: RequestInit): Promise<getReportResponse> => {
|
||||||
|
|
@ -78,7 +78,7 @@ export const getReport = async (runId: string, options?: RequestInit): Promise<g
|
||||||
|
|
||||||
export const getGetReportQueryKey = (runId?: string,) => {
|
export const getGetReportQueryKey = (runId?: string,) => {
|
||||||
return [
|
return [
|
||||||
`/api/reports/${runId}`
|
`/api/report/${runId}`
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ConversionStrategy {
|
export interface AdditionalDomain {
|
||||||
summary: string;
|
domain: string;
|
||||||
actions: string[];
|
purpose: string;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type AnnotationType = typeof AnnotationType[keyof typeof AnnotationType];
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const AnnotationType = {
|
||||||
|
highlight: 'highlight',
|
||||||
|
arrow: 'arrow',
|
||||||
|
text: 'text',
|
||||||
|
} as const;
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
import type { PlanOutput } from './planOutput';
|
|
||||||
|
|
||||||
export type GetPlan200 = PlanOutput | null;
|
export interface AsIsToBeItem {
|
||||||
|
area: string;
|
||||||
|
asIs: string;
|
||||||
|
toBe: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { FileType } from './fileType';
|
||||||
|
|
||||||
|
export interface BodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost {
|
||||||
|
/** 업로드할 파일 */
|
||||||
|
file: string;
|
||||||
|
/** 파일 타입 (image/video/audio/document/file) */
|
||||||
|
file_type?: FileType;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
import type { ReportOutput } from './reportOutput';
|
|
||||||
|
|
||||||
export type GetReport200 = ReportOutput | null;
|
export interface BrandColors {
|
||||||
|
primary: string;
|
||||||
|
accent: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import type { FontSpec } from './fontSpec';
|
||||||
import type { LogoUsageRule } from './logoUsageRule';
|
import type { LogoUsageRule } from './logoUsageRule';
|
||||||
import type { ToneOfVoice } from './toneOfVoice';
|
import type { ToneOfVoice } from './toneOfVoice';
|
||||||
import type { ChannelBrandingRule } from './channelBrandingRule';
|
import type { ChannelBrandingRule } from './channelBrandingRule';
|
||||||
import type { BrandInconsistency } from './brandInconsistency';
|
import type { BrandPlanInconsistency } from './brandPlanInconsistency';
|
||||||
|
|
||||||
export interface BrandGuide {
|
export interface BrandGuide {
|
||||||
colors: ColorSwatch[];
|
colors: ColorSwatch[];
|
||||||
|
|
@ -17,5 +17,5 @@ export interface BrandGuide {
|
||||||
logoRules: LogoUsageRule[];
|
logoRules: LogoUsageRule[];
|
||||||
toneOfVoice: ToneOfVoice;
|
toneOfVoice: ToneOfVoice;
|
||||||
channelBranding: ChannelBrandingRule[];
|
channelBranding: ChannelBrandingRule[];
|
||||||
brandInconsistencies: BrandInconsistency[];
|
brandInconsistencies: BrandPlanInconsistency[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { BrandPlanInconsistencyValue } from './brandPlanInconsistencyValue';
|
||||||
|
|
||||||
|
export interface BrandPlanInconsistency {
|
||||||
|
field: string;
|
||||||
|
values: BrandPlanInconsistencyValue[];
|
||||||
|
impact: string;
|
||||||
|
recommendation: string;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
import type { ChannelScore } from './channelScore';
|
|
||||||
|
|
||||||
export type ReportOutputYoutube = ChannelScore | null;
|
export interface BrandPlanInconsistencyValue {
|
||||||
|
channel: string;
|
||||||
|
value: string;
|
||||||
|
isCorrect: boolean;
|
||||||
|
}
|
||||||
|
|
@ -4,10 +4,13 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
|
import type { Severity } from './severity';
|
||||||
|
|
||||||
export interface ChannelScore {
|
export interface ChannelScore {
|
||||||
|
channel: string;
|
||||||
|
icon: string;
|
||||||
score: number;
|
score: number;
|
||||||
summary: string;
|
maxScore: number;
|
||||||
strengths: string[];
|
status: Severity;
|
||||||
weaknesses: string[];
|
headline: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ChannelStatus = typeof ChannelStatus[keyof typeof ChannelStatus];
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const ChannelStatus = {
|
||||||
|
active: 'active',
|
||||||
|
inactive: 'inactive',
|
||||||
|
unknown: 'unknown',
|
||||||
|
not_found: 'not_found',
|
||||||
|
} as const;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ClinicListItemHospitalNameEn } from './clinicListItemHospitalNameEn';
|
||||||
|
import type { ClinicListItemRoadAddress } from './clinicListItemRoadAddress';
|
||||||
|
import type { ClinicListItemUrl } from './clinicListItemUrl';
|
||||||
|
|
||||||
|
export interface ClinicListItem {
|
||||||
|
hospital_id: string;
|
||||||
|
hospital_name: string;
|
||||||
|
hospital_name_en: ClinicListItemHospitalNameEn;
|
||||||
|
road_address: ClinicListItemRoadAddress;
|
||||||
|
url: ClinicListItemUrl;
|
||||||
|
status: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ClinicListItemHospitalNameEn = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ClinicListItemRoadAddress = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ClinicListItemUrl = string | null;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ClinicListItem } from './clinicListItem';
|
||||||
|
|
||||||
|
export interface ClinicListResponse {
|
||||||
|
items: ClinicListItem[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { LeadDoctor } from './leadDoctor';
|
||||||
|
import type { PriceRange } from './priceRange';
|
||||||
|
import type { ClinicSnapshotLogoImages } from './clinicSnapshotLogoImages';
|
||||||
|
import type { ClinicSnapshotBrandColors } from './clinicSnapshotBrandColors';
|
||||||
|
import type { ClinicSnapshotSource } from './clinicSnapshotSource';
|
||||||
|
import type { ClinicSnapshotRegistryData } from './clinicSnapshotRegistryData';
|
||||||
|
|
||||||
|
export interface ClinicSnapshot {
|
||||||
|
name: string;
|
||||||
|
nameEn: string;
|
||||||
|
established: string;
|
||||||
|
yearsInBusiness: number;
|
||||||
|
staffCount: number;
|
||||||
|
leadDoctor: LeadDoctor;
|
||||||
|
overallRating: number;
|
||||||
|
totalReviews: number;
|
||||||
|
priceRange: PriceRange;
|
||||||
|
certifications: string[];
|
||||||
|
mediaAppearances: string[];
|
||||||
|
medicalTourism: string[];
|
||||||
|
location: string;
|
||||||
|
nearestStation: string;
|
||||||
|
phone: string;
|
||||||
|
domain: string;
|
||||||
|
logoImages?: ClinicSnapshotLogoImages;
|
||||||
|
brandColors?: ClinicSnapshotBrandColors;
|
||||||
|
source?: ClinicSnapshotSource;
|
||||||
|
registryData?: ClinicSnapshotRegistryData;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,6 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
import type { ChannelScore } from './channelScore';
|
import type { BrandColors } from './brandColors';
|
||||||
|
|
||||||
export type ReportOutputFacebook = ChannelScore | null;
|
export type ClinicSnapshotBrandColors = BrandColors | null;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { LogoImages } from './logoImages';
|
||||||
|
|
||||||
|
export type ClinicSnapshotLogoImages = LogoImages | null;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { RegistryData } from './registryData';
|
||||||
|
|
||||||
|
export type ClinicSnapshotRegistryData = RegistryData | null;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { DataSource } from './dataSource';
|
||||||
|
|
||||||
|
export type ClinicSnapshotSource = DataSource | null;
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
export interface ContentPillar {
|
export interface ContentPillar {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
relatedUSP: string;
|
relatedUsp: string;
|
||||||
exampleTopics: string[];
|
exampleTopics: string[];
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type DataSource = typeof DataSource[keyof typeof DataSource];
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const DataSource = {
|
||||||
|
registry: 'registry',
|
||||||
|
scrape: 'scrape',
|
||||||
|
} as const;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { Severity } from './severity';
|
||||||
|
import type { DiagnosisItemEvidenceIds } from './diagnosisItemEvidenceIds';
|
||||||
|
|
||||||
|
export interface DiagnosisItem {
|
||||||
|
category: string;
|
||||||
|
detail: string;
|
||||||
|
severity: Severity;
|
||||||
|
evidenceIds?: DiagnosisItemEvidenceIds;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type DiagnosisItemEvidenceIds = string[] | null;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface EstimatedRevenue {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { FacebookPage } from './facebookPage';
|
||||||
|
import type { DiagnosisItem } from './diagnosisItem';
|
||||||
|
import type { BrandInconsistency } from './brandInconsistency';
|
||||||
|
|
||||||
|
export interface FacebookAudit {
|
||||||
|
pages: FacebookPage[];
|
||||||
|
diagnosis: DiagnosisItem[];
|
||||||
|
brandInconsistencies: BrandInconsistency[];
|
||||||
|
consolidationRecommendation: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { Language } from './language';
|
||||||
|
import type { FacebookPagePostFrequency } from './facebookPagePostFrequency';
|
||||||
|
import type { FacebookPageTopContentType } from './facebookPageTopContentType';
|
||||||
|
import type { FacebookPageEngagement } from './facebookPageEngagement';
|
||||||
|
|
||||||
|
export interface FacebookPage {
|
||||||
|
url: string;
|
||||||
|
pageName: string;
|
||||||
|
language: Language;
|
||||||
|
label: string;
|
||||||
|
followers: number;
|
||||||
|
following: number;
|
||||||
|
category: string;
|
||||||
|
bio: string;
|
||||||
|
logo: string;
|
||||||
|
logoDescription: string;
|
||||||
|
link: string;
|
||||||
|
linkedDomain: string;
|
||||||
|
reviews: number;
|
||||||
|
recentPostAge: string;
|
||||||
|
hasWhatsapp: boolean;
|
||||||
|
postFrequency?: FacebookPagePostFrequency;
|
||||||
|
topContentType?: FacebookPageTopContentType;
|
||||||
|
engagement?: FacebookPageEngagement;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FacebookPageEngagement = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FacebookPagePostFrequency = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FacebookPageTopContentType = string | null;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { FileType } from './fileType';
|
||||||
|
import type { FileListItemSizeBytes } from './fileListItemSizeBytes';
|
||||||
|
|
||||||
|
export interface FileListItem {
|
||||||
|
id: number;
|
||||||
|
file_type: FileType;
|
||||||
|
file_name: string;
|
||||||
|
file_url: string;
|
||||||
|
size_bytes?: FileListItemSizeBytes;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FileListItemSizeBytes = number | null;
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FileType = typeof FileType[keyof typeof FileType];
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const FileType = {
|
||||||
|
image: 'image',
|
||||||
|
video: 'video',
|
||||||
|
audio: 'audio',
|
||||||
|
document: 'document',
|
||||||
|
file: 'file',
|
||||||
|
} as const;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { FileType } from './fileType';
|
||||||
|
import type { FileUploadResponseSizeBytes } from './fileUploadResponseSizeBytes';
|
||||||
|
|
||||||
|
export interface FileUploadResponse {
|
||||||
|
id: number;
|
||||||
|
analysis_run_id: string;
|
||||||
|
file_type: FileType;
|
||||||
|
file_name: string;
|
||||||
|
file_url: string;
|
||||||
|
size_bytes?: FileUploadResponseSizeBytes;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FileUploadResponseSizeBytes = number | null;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type GetClinicsParams = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from './additionalDomain';
|
||||||
export * from './analysisCreate';
|
export * from './analysisCreate';
|
||||||
export * from './analysisOptions';
|
export * from './analysisOptions';
|
||||||
export * from './analysisStartResponse';
|
export * from './analysisStartResponse';
|
||||||
|
|
@ -12,14 +13,20 @@ export * from './analysisStatus';
|
||||||
export * from './analysisStatusResponse';
|
export * from './analysisStatusResponse';
|
||||||
export * from './analysisStatusResponseChannelErrors';
|
export * from './analysisStatusResponseChannelErrors';
|
||||||
export * from './analysisStatusResponseCompletedAt';
|
export * from './analysisStatusResponseCompletedAt';
|
||||||
|
export * from './annotationType';
|
||||||
|
export * from './asIsToBeItem';
|
||||||
export * from './assetCard';
|
export * from './assetCard';
|
||||||
export * from './assetCardSource';
|
export * from './assetCardSource';
|
||||||
export * from './assetCardStatus';
|
export * from './assetCardStatus';
|
||||||
export * from './assetCardType';
|
export * from './assetCardType';
|
||||||
export * from './assetCollectionData';
|
export * from './assetCollectionData';
|
||||||
|
export * from './bodyUploadAnalysisRunFileApiAnalysisRunIdFilesPost';
|
||||||
|
export * from './brandColors';
|
||||||
export * from './brandGuide';
|
export * from './brandGuide';
|
||||||
export * from './brandInconsistency';
|
export * from './brandInconsistency';
|
||||||
export * from './brandInconsistencyValue';
|
export * from './brandInconsistencyValue';
|
||||||
|
export * from './brandPlanInconsistency';
|
||||||
|
export * from './brandPlanInconsistencyValue';
|
||||||
export * from './calendarData';
|
export * from './calendarData';
|
||||||
export * from './calendarEntry';
|
export * from './calendarEntry';
|
||||||
export * from './calendarEntryAiPromptSeed';
|
export * from './calendarEntryAiPromptSeed';
|
||||||
|
|
@ -33,6 +40,7 @@ export * from './calendarWeek';
|
||||||
export * from './channelBrandingRule';
|
export * from './channelBrandingRule';
|
||||||
export * from './channelBrandingRuleCurrentStatus';
|
export * from './channelBrandingRuleCurrentStatus';
|
||||||
export * from './channelScore';
|
export * from './channelScore';
|
||||||
|
export * from './channelStatus';
|
||||||
export * from './channelStrategyCard';
|
export * from './channelStrategyCard';
|
||||||
export * from './channelStrategyCardCustomerJourneyStage';
|
export * from './channelStrategyCardCustomerJourneyStage';
|
||||||
export * from './channelStrategyCardPriority';
|
export * from './channelStrategyCardPriority';
|
||||||
|
|
@ -46,43 +54,111 @@ export * from './clinicCreate';
|
||||||
export * from './clinicCreateResponse';
|
export * from './clinicCreateResponse';
|
||||||
export * from './clinicHistoryResponse';
|
export * from './clinicHistoryResponse';
|
||||||
export * from './clinicHistoryResponseMetricsTimeseries';
|
export * from './clinicHistoryResponseMetricsTimeseries';
|
||||||
|
export * from './clinicListItem';
|
||||||
|
export * from './clinicListItemHospitalNameEn';
|
||||||
|
export * from './clinicListItemRoadAddress';
|
||||||
|
export * from './clinicListItemUrl';
|
||||||
|
export * from './clinicListResponse';
|
||||||
export * from './clinicResponse';
|
export * from './clinicResponse';
|
||||||
export * from './clinicResponseHospitalNameEn';
|
export * from './clinicResponseHospitalNameEn';
|
||||||
export * from './clinicResponseRawData';
|
export * from './clinicResponseRawData';
|
||||||
export * from './clinicResponseRawDataAnyOf';
|
export * from './clinicResponseRawDataAnyOf';
|
||||||
export * from './clinicResponseRoadAddress';
|
export * from './clinicResponseRoadAddress';
|
||||||
export * from './clinicResponseUrl';
|
export * from './clinicResponseUrl';
|
||||||
|
export * from './clinicSnapshot';
|
||||||
|
export * from './clinicSnapshotBrandColors';
|
||||||
|
export * from './clinicSnapshotLogoImages';
|
||||||
|
export * from './clinicSnapshotRegistryData';
|
||||||
|
export * from './clinicSnapshotSource';
|
||||||
export * from './colorSwatch';
|
export * from './colorSwatch';
|
||||||
export * from './contentCountSummary';
|
export * from './contentCountSummary';
|
||||||
export * from './contentCountSummaryType';
|
export * from './contentCountSummaryType';
|
||||||
export * from './contentPillar';
|
export * from './contentPillar';
|
||||||
export * from './contentStrategyData';
|
export * from './contentStrategyData';
|
||||||
export * from './contentTypeRow';
|
export * from './contentTypeRow';
|
||||||
export * from './conversionStrategy';
|
export * from './dataSource';
|
||||||
|
export * from './diagnosisItem';
|
||||||
|
export * from './diagnosisItemEvidenceIds';
|
||||||
|
export * from './estimatedRevenue';
|
||||||
|
export * from './facebookAudit';
|
||||||
|
export * from './facebookPage';
|
||||||
|
export * from './facebookPageEngagement';
|
||||||
|
export * from './facebookPagePostFrequency';
|
||||||
|
export * from './facebookPageTopContentType';
|
||||||
|
export * from './fileListItem';
|
||||||
|
export * from './fileListItemSizeBytes';
|
||||||
|
export * from './fileType';
|
||||||
|
export * from './fileUploadResponse';
|
||||||
|
export * from './fileUploadResponseSizeBytes';
|
||||||
export * from './fontSpec';
|
export * from './fontSpec';
|
||||||
export * from './getPlan200';
|
export * from './getClinicsParams';
|
||||||
export * from './getReport200';
|
|
||||||
export * from './hTTPValidationError';
|
export * from './hTTPValidationError';
|
||||||
|
export * from './instagramAccount';
|
||||||
|
export * from './instagramAudit';
|
||||||
|
export * from './kPIMetric';
|
||||||
|
export * from './language';
|
||||||
|
export * from './leadDoctor';
|
||||||
|
export * from './linkedUrl';
|
||||||
|
export * from './logoImages';
|
||||||
|
export * from './logoImagesCircle';
|
||||||
|
export * from './logoImagesHorizontal';
|
||||||
|
export * from './logoImagesKorean';
|
||||||
export * from './logoUsageRule';
|
export * from './logoUsageRule';
|
||||||
export * from './planOutput';
|
export * from './marketingReportResponse';
|
||||||
export * from './planOutputRepurposingProposals';
|
export * from './marketingReportResponseClinicName';
|
||||||
export * from './reportOutput';
|
export * from './marketingReportResponseClinicNameEn';
|
||||||
export * from './reportOutputFacebook';
|
export * from './marketingReportResponseScreenshots';
|
||||||
export * from './reportOutputGangnamUnni';
|
export * from './newChannelProposal';
|
||||||
export * from './reportOutputInstagram';
|
export * from './otherChannel';
|
||||||
export * from './reportOutputNaverBlog';
|
export * from './otherChannelUrl';
|
||||||
export * from './reportOutputYoutube';
|
export * from './planApiResponse';
|
||||||
|
export * from './planApiResponseClinicName';
|
||||||
|
export * from './planApiResponseClinicNameEn';
|
||||||
|
export * from './planApiResponseRepurposingProposals';
|
||||||
|
export * from './platformStrategy';
|
||||||
|
export * from './priceRange';
|
||||||
|
export * from './registryData';
|
||||||
|
export * from './registryDataBranches';
|
||||||
|
export * from './registryDataBrandGroup';
|
||||||
|
export * from './registryDataDistrict';
|
||||||
|
export * from './registryDataGangnamUnniUrl';
|
||||||
|
export * from './registryDataGoogleMapsUrl';
|
||||||
|
export * from './registryDataNaverPlaceUrl';
|
||||||
|
export * from './registryDataWebsiteEn';
|
||||||
export * from './repurposingOutput';
|
export * from './repurposingOutput';
|
||||||
export * from './repurposingProposalItem';
|
export * from './repurposingProposalItem';
|
||||||
export * from './repurposingProposalItemEstimatedEffort';
|
export * from './repurposingProposalItemEstimatedEffort';
|
||||||
export * from './repurposingProposalItemPriority';
|
export * from './repurposingProposalItemPriority';
|
||||||
|
export * from './roadmapMonth';
|
||||||
|
export * from './roadmapTask';
|
||||||
export * from './runSummary';
|
export * from './runSummary';
|
||||||
export * from './runSummaryCompletedAt';
|
export * from './runSummaryCompletedAt';
|
||||||
export * from './runSummaryOverallScore';
|
export * from './runSummaryOverallScore';
|
||||||
|
export * from './screenshotAnnotation';
|
||||||
|
export * from './screenshotAnnotationColor';
|
||||||
|
export * from './screenshotAnnotationHeight';
|
||||||
|
export * from './screenshotAnnotationLabel';
|
||||||
|
export * from './screenshotAnnotationWidth';
|
||||||
|
export * from './screenshotEvidence';
|
||||||
|
export * from './screenshotEvidenceAnnotations';
|
||||||
|
export * from './screenshotEvidenceSourceUrl';
|
||||||
|
export * from './severity';
|
||||||
|
export * from './snsLink';
|
||||||
|
export * from './strategyDetail';
|
||||||
export * from './toneOfVoice';
|
export * from './toneOfVoice';
|
||||||
|
export * from './topVideo';
|
||||||
|
export * from './topVideoDuration';
|
||||||
|
export * from './trackingPixel';
|
||||||
|
export * from './trackingPixelDetails';
|
||||||
|
export * from './transformationProposal';
|
||||||
export * from './validationError';
|
export * from './validationError';
|
||||||
export * from './validationErrorCtx';
|
export * from './validationErrorCtx';
|
||||||
export * from './validationErrorLocItem';
|
export * from './validationErrorLocItem';
|
||||||
|
export * from './videoType';
|
||||||
|
export * from './websiteAudit';
|
||||||
|
export * from './websiteAuditSnsLinksDetail';
|
||||||
|
export * from './weeklyViewGrowth';
|
||||||
export * from './workflowStep';
|
export * from './workflowStep';
|
||||||
|
export * from './youTubeAudit';
|
||||||
export * from './youTubeRepurposeItem';
|
export * from './youTubeRepurposeItem';
|
||||||
export * from './youTubeRepurposeItemType';
|
export * from './youTubeRepurposeItemType';
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { Language } from './language';
|
||||||
|
|
||||||
|
export interface InstagramAccount {
|
||||||
|
handle: string;
|
||||||
|
language: Language;
|
||||||
|
label: string;
|
||||||
|
posts: number;
|
||||||
|
followers: number;
|
||||||
|
following: number;
|
||||||
|
category: string;
|
||||||
|
profileLink: string;
|
||||||
|
highlights: string[];
|
||||||
|
reelsCount: number;
|
||||||
|
contentFormat: string;
|
||||||
|
profilePhoto: string;
|
||||||
|
bio: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { InstagramAccount } from './instagramAccount';
|
||||||
|
import type { DiagnosisItem } from './diagnosisItem';
|
||||||
|
|
||||||
|
export interface InstagramAudit {
|
||||||
|
accounts: InstagramAccount[];
|
||||||
|
diagnosis: DiagnosisItem[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface KPIMetric {
|
||||||
|
metric: string;
|
||||||
|
current: string;
|
||||||
|
target3Month: string;
|
||||||
|
target12Month: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Language = typeof Language[keyof typeof Language];
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||||
|
export const Language = {
|
||||||
|
KR: 'KR',
|
||||||
|
EN: 'EN',
|
||||||
|
} as const;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface LeadDoctor {
|
||||||
|
name: string;
|
||||||
|
credentials: string;
|
||||||
|
rating: number;
|
||||||
|
reviewCount: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface LinkedUrl {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { LogoImagesCircle } from './logoImagesCircle';
|
||||||
|
import type { LogoImagesHorizontal } from './logoImagesHorizontal';
|
||||||
|
import type { LogoImagesKorean } from './logoImagesKorean';
|
||||||
|
|
||||||
|
export interface LogoImages {
|
||||||
|
circle?: LogoImagesCircle;
|
||||||
|
horizontal?: LogoImagesHorizontal;
|
||||||
|
korean?: LogoImagesKorean;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type LogoImagesCircle = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type LogoImagesHorizontal = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type LogoImagesKorean = string | null;
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { MarketingReportResponseClinicName } from './marketingReportResponseClinicName';
|
||||||
|
import type { MarketingReportResponseClinicNameEn } from './marketingReportResponseClinicNameEn';
|
||||||
|
import type { ClinicSnapshot } from './clinicSnapshot';
|
||||||
|
import type { ChannelScore } from './channelScore';
|
||||||
|
import type { YouTubeAudit } from './youTubeAudit';
|
||||||
|
import type { InstagramAudit } from './instagramAudit';
|
||||||
|
import type { FacebookAudit } from './facebookAudit';
|
||||||
|
import type { OtherChannel } from './otherChannel';
|
||||||
|
import type { WebsiteAudit } from './websiteAudit';
|
||||||
|
import type { DiagnosisItem } from './diagnosisItem';
|
||||||
|
import type { TransformationProposal } from './transformationProposal';
|
||||||
|
import type { RoadmapMonth } from './roadmapMonth';
|
||||||
|
import type { KPIMetric } from './kPIMetric';
|
||||||
|
import type { MarketingReportResponseScreenshots } from './marketingReportResponseScreenshots';
|
||||||
|
|
||||||
|
export interface MarketingReportResponse {
|
||||||
|
id: string;
|
||||||
|
clinicName?: MarketingReportResponseClinicName;
|
||||||
|
clinicNameEn?: MarketingReportResponseClinicNameEn;
|
||||||
|
createdAt: string;
|
||||||
|
targetUrl: string;
|
||||||
|
overallScore: number;
|
||||||
|
clinicSnapshot: ClinicSnapshot;
|
||||||
|
channelScores: ChannelScore[];
|
||||||
|
youtubeAudit: YouTubeAudit;
|
||||||
|
instagramAudit: InstagramAudit;
|
||||||
|
facebookAudit: FacebookAudit;
|
||||||
|
otherChannels: OtherChannel[];
|
||||||
|
websiteAudit: WebsiteAudit;
|
||||||
|
problemDiagnosis: DiagnosisItem[];
|
||||||
|
transformation: TransformationProposal;
|
||||||
|
roadmap: RoadmapMonth[];
|
||||||
|
kpiDashboard: KPIMetric[];
|
||||||
|
screenshots?: MarketingReportResponseScreenshots;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type MarketingReportResponseClinicName = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type MarketingReportResponseClinicNameEn = string | null;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ScreenshotEvidence } from './screenshotEvidence';
|
||||||
|
|
||||||
|
export type MarketingReportResponseScreenshots = ScreenshotEvidence[] | null;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface NewChannelProposal {
|
||||||
|
channel: string;
|
||||||
|
priority: string;
|
||||||
|
rationale: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { ChannelStatus } from './channelStatus';
|
||||||
|
import type { OtherChannelUrl } from './otherChannelUrl';
|
||||||
|
|
||||||
|
export interface OtherChannel {
|
||||||
|
name: string;
|
||||||
|
status: ChannelStatus;
|
||||||
|
details: string;
|
||||||
|
url?: OtherChannelUrl;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type OtherChannelUrl = string | null;
|
||||||
|
|
@ -4,18 +4,25 @@
|
||||||
* FastAPI
|
* FastAPI
|
||||||
* OpenAPI spec version: 0.1.0
|
* OpenAPI spec version: 0.1.0
|
||||||
*/
|
*/
|
||||||
|
import type { PlanApiResponseClinicName } from './planApiResponseClinicName';
|
||||||
|
import type { PlanApiResponseClinicNameEn } from './planApiResponseClinicNameEn';
|
||||||
import type { BrandGuide } from './brandGuide';
|
import type { BrandGuide } from './brandGuide';
|
||||||
import type { ChannelStrategyCard } from './channelStrategyCard';
|
import type { ChannelStrategyCard } from './channelStrategyCard';
|
||||||
import type { ContentStrategyData } from './contentStrategyData';
|
import type { ContentStrategyData } from './contentStrategyData';
|
||||||
import type { CalendarData } from './calendarData';
|
import type { CalendarData } from './calendarData';
|
||||||
import type { AssetCollectionData } from './assetCollectionData';
|
import type { AssetCollectionData } from './assetCollectionData';
|
||||||
import type { PlanOutputRepurposingProposals } from './planOutputRepurposingProposals';
|
import type { PlanApiResponseRepurposingProposals } from './planApiResponseRepurposingProposals';
|
||||||
|
|
||||||
export interface PlanOutput {
|
export interface PlanApiResponse {
|
||||||
|
id: string;
|
||||||
|
clinicName?: PlanApiResponseClinicName;
|
||||||
|
clinicNameEn?: PlanApiResponseClinicNameEn;
|
||||||
|
createdAt: string;
|
||||||
|
targetUrl: string;
|
||||||
brandGuide: BrandGuide;
|
brandGuide: BrandGuide;
|
||||||
channelStrategies: ChannelStrategyCard[];
|
channelStrategies: ChannelStrategyCard[];
|
||||||
contentStrategy: ContentStrategyData;
|
contentStrategy: ContentStrategyData;
|
||||||
calendar: CalendarData;
|
calendar: CalendarData;
|
||||||
assetCollection: AssetCollectionData;
|
assetCollection: AssetCollectionData;
|
||||||
repurposingProposals?: PlanOutputRepurposingProposals;
|
repurposingProposals?: PlanApiResponseRepurposingProposals;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type PlanApiResponseClinicName = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type PlanApiResponseClinicNameEn = string | null;
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
*/
|
*/
|
||||||
import type { RepurposingProposalItem } from './repurposingProposalItem';
|
import type { RepurposingProposalItem } from './repurposingProposalItem';
|
||||||
|
|
||||||
export type PlanOutputRepurposingProposals = RepurposingProposalItem[] | null;
|
export type PlanApiResponseRepurposingProposals = RepurposingProposalItem[] | null;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { StrategyDetail } from './strategyDetail';
|
||||||
|
|
||||||
|
export interface PlatformStrategy {
|
||||||
|
platform: string;
|
||||||
|
icon: string;
|
||||||
|
currentMetric: string;
|
||||||
|
targetMetric: string;
|
||||||
|
strategies: StrategyDetail[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface PriceRange {
|
||||||
|
min: string;
|
||||||
|
max: string;
|
||||||
|
currency: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { RegistryDataDistrict } from './registryDataDistrict';
|
||||||
|
import type { RegistryDataBranches } from './registryDataBranches';
|
||||||
|
import type { RegistryDataBrandGroup } from './registryDataBrandGroup';
|
||||||
|
import type { RegistryDataWebsiteEn } from './registryDataWebsiteEn';
|
||||||
|
import type { RegistryDataNaverPlaceUrl } from './registryDataNaverPlaceUrl';
|
||||||
|
import type { RegistryDataGangnamUnniUrl } from './registryDataGangnamUnniUrl';
|
||||||
|
import type { RegistryDataGoogleMapsUrl } from './registryDataGoogleMapsUrl';
|
||||||
|
|
||||||
|
export interface RegistryData {
|
||||||
|
district?: RegistryDataDistrict;
|
||||||
|
branches?: RegistryDataBranches;
|
||||||
|
brandGroup?: RegistryDataBrandGroup;
|
||||||
|
websiteEn?: RegistryDataWebsiteEn;
|
||||||
|
naverPlaceUrl?: RegistryDataNaverPlaceUrl;
|
||||||
|
gangnamUnniUrl?: RegistryDataGangnamUnniUrl;
|
||||||
|
googleMapsUrl?: RegistryDataGoogleMapsUrl;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataBranches = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataBrandGroup = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataDistrict = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataGangnamUnniUrl = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataGoogleMapsUrl = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataNaverPlaceUrl = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type RegistryDataWebsiteEn = string | null;
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
* Generated by orval v7.21.0 🍺
|
|
||||||
* Do not edit manually.
|
|
||||||
* FastAPI
|
|
||||||
* OpenAPI spec version: 0.1.0
|
|
||||||
*/
|
|
||||||
import type { ReportOutputInstagram } from './reportOutputInstagram';
|
|
||||||
import type { ReportOutputFacebook } from './reportOutputFacebook';
|
|
||||||
import type { ReportOutputNaverBlog } from './reportOutputNaverBlog';
|
|
||||||
import type { ReportOutputYoutube } from './reportOutputYoutube';
|
|
||||||
import type { ReportOutputGangnamUnni } from './reportOutputGangnamUnni';
|
|
||||||
import type { ConversionStrategy } from './conversionStrategy';
|
|
||||||
|
|
||||||
export interface ReportOutput {
|
|
||||||
overall_score: number;
|
|
||||||
instagram?: ReportOutputInstagram;
|
|
||||||
facebook?: ReportOutputFacebook;
|
|
||||||
naver_blog?: ReportOutputNaverBlog;
|
|
||||||
youtube?: ReportOutputYoutube;
|
|
||||||
gangnam_unni?: ReportOutputGangnamUnni;
|
|
||||||
conversion_strategy: ConversionStrategy;
|
|
||||||
roadmap: string[];
|
|
||||||
kpis: string[];
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/**
|
|
||||||
* Generated by orval v7.21.0 🍺
|
|
||||||
* Do not edit manually.
|
|
||||||
* FastAPI
|
|
||||||
* OpenAPI spec version: 0.1.0
|
|
||||||
*/
|
|
||||||
import type { ChannelScore } from './channelScore';
|
|
||||||
|
|
||||||
export type ReportOutputGangnamUnni = ChannelScore | null;
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/**
|
|
||||||
* Generated by orval v7.21.0 🍺
|
|
||||||
* Do not edit manually.
|
|
||||||
* FastAPI
|
|
||||||
* OpenAPI spec version: 0.1.0
|
|
||||||
*/
|
|
||||||
import type { ChannelScore } from './channelScore';
|
|
||||||
|
|
||||||
export type ReportOutputInstagram = ChannelScore | null;
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/**
|
|
||||||
* Generated by orval v7.21.0 🍺
|
|
||||||
* Do not edit manually.
|
|
||||||
* FastAPI
|
|
||||||
* OpenAPI spec version: 0.1.0
|
|
||||||
*/
|
|
||||||
import type { ChannelScore } from './channelScore';
|
|
||||||
|
|
||||||
export type ReportOutputNaverBlog = ChannelScore | null;
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { RoadmapTask } from './roadmapTask';
|
||||||
|
|
||||||
|
export interface RoadmapMonth {
|
||||||
|
month: number;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
tasks: RoadmapTask[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface RoadmapTask {
|
||||||
|
task: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
import type { AnnotationType } from './annotationType';
|
||||||
|
import type { ScreenshotAnnotationWidth } from './screenshotAnnotationWidth';
|
||||||
|
import type { ScreenshotAnnotationHeight } from './screenshotAnnotationHeight';
|
||||||
|
import type { ScreenshotAnnotationLabel } from './screenshotAnnotationLabel';
|
||||||
|
import type { ScreenshotAnnotationColor } from './screenshotAnnotationColor';
|
||||||
|
|
||||||
|
export interface ScreenshotAnnotation {
|
||||||
|
type: AnnotationType;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width?: ScreenshotAnnotationWidth;
|
||||||
|
height?: ScreenshotAnnotationHeight;
|
||||||
|
label?: ScreenshotAnnotationLabel;
|
||||||
|
color?: ScreenshotAnnotationColor;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ScreenshotAnnotationColor = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ScreenshotAnnotationHeight = number | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ScreenshotAnnotationLabel = string | null;
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Generated by orval v7.21.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* FastAPI
|
||||||
|
* OpenAPI spec version: 0.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ScreenshotAnnotationWidth = number | null;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue