chore: clinics 페이지 갱신 (AnalysisCard / AnalysisTab / SettingsTab / ClinicProfilePage / routes)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>main
parent
0b139714ed
commit
e8ec9e2075
|
|
@ -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 /> },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue