finyx_data_frontend/src/pages/EngagementView.tsx
2026-01-07 08:13:50 +08:00

181 lines
7.2 KiB
TypeScript

import React, { useState } from 'react';
import {
ArrowLeft,
ChevronRight,
Sparkles,
EyeOff,
CheckCircle2
} from 'lucide-react';
import { ViewMode, Step, InventoryMode, Scenario } from '../types';
import { SetupStep } from './engagement/SetupStep';
import { InventoryStep } from './engagement/InventoryStep';
import { ContextStep } from './engagement/ContextStep';
import { ValueStep } from './engagement/ValueStep';
import { DeliveryStep } from './engagement/DeliveryStep';
import { scenarioData as initialScenarioData } from '../data/mockData';
interface EngagementViewProps {
currentStep: Step;
setCurrentStep: (step: Step) => void;
setCurrentView: (view: ViewMode) => void;
isPresentationMode: boolean;
setIsPresentationMode: (mode: boolean) => void;
}
export const EngagementView: React.FC<EngagementViewProps> = ({
currentStep,
setCurrentStep,
setCurrentView,
isPresentationMode,
setIsPresentationMode,
}) => {
const [inventoryMode, setInventoryMode] = useState<InventoryMode>('selection');
// Initialize with scenarios that are marked as selected in mock data
const [selectedScenarios, setSelectedScenarios] = useState<Scenario[]>(
initialScenarioData.filter(s => s.selected).map(s => ({ ...s }))
);
// Toggle scenario selection
const toggleScenarioSelection = (scenarioId: number) => {
setSelectedScenarios(prev => {
const isSelected = prev.some(s => s.id === scenarioId);
if (isSelected) {
// Remove from selection
return prev.filter(s => s.id !== scenarioId);
} else {
// Add to selection - get full scenario data from initial data
const scenario = initialScenarioData.find(s => s.id === scenarioId);
if (!scenario) return prev;
return [...prev, { ...scenario, selected: true }];
}
});
};
// Stepper Configuration
const steps = [
{ id: 'setup', label: '1. 项目配置' },
{ id: 'inventory', label: '2. 数据盘点' },
{ id: 'context', label: '3. 背景调研' },
{ id: 'value', label: '4. 价值挖掘' },
{ id: 'delivery', label: '5. 成果交付' },
];
const currentStepIndex = steps.findIndex(s => s.id === currentStep);
return (
<div className={`bg-slate-50 h-full flex flex-col ${isPresentationMode ? 'p-0' : 'p-6'} overflow-hidden`}>
{/* Project Header */}
{!isPresentationMode && (
<div className="mb-6 flex items-center justify-between">
<div>
{/* Back to Project List Navigation */}
<div className="flex items-center text-sm text-slate-500 mb-1 cursor-pointer hover:text-blue-600 transition-colors" onClick={() => setCurrentView('projects')}>
<ArrowLeft size={14} className="mr-1" />
<span></span>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="flex -space-x-2">
<div className="w-8 h-8 rounded-full bg-blue-100 border-2 border-white flex items-center justify-center text-xs font-bold text-blue-800" title="Project Manager">PM</div>
<div className="w-8 h-8 rounded-full bg-purple-100 border-2 border-white flex items-center justify-center text-xs font-bold text-purple-800" title="Data Analyst">DA</div>
<div className="w-8 h-8 rounded-full bg-gray-100 border-2 border-white flex items-center justify-center text-xs text-gray-500">+2</div>
</div>
<div className="h-6 w-px bg-slate-300"></div>
<button
onClick={() => setIsPresentationMode(true)}
className="flex items-center px-4 py-2 bg-slate-900 text-white rounded-md text-sm hover:bg-slate-700 shadow-lg transition-all"
>
<Sparkles size={16} className="mr-2" />
</button>
</div>
</div>
)}
{/* Stepper */}
<div className={`bg-white border border-slate-200 rounded-lg mb-6 ${isPresentationMode ? 'hidden' : 'block'}`}>
<div className="flex items-center p-4">
{steps.map((step, idx) => {
const isActive = step.id === currentStep;
const isCompleted = idx < currentStepIndex;
return (
<div key={step.id} className="flex items-center flex-1">
<div
onClick={() => {
setCurrentStep(step.id as Step);
// Reset inventory mode if returning to that step
if (step.id === 'inventory' && inventoryMode === 'results') {
// keep results
} else if (step.id === 'inventory') {
setInventoryMode('selection');
}
}}
className={`flex items-center cursor-pointer group ${idx === steps.length - 1 ? 'flex-none' : 'w-full'}`}
>
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold border-2 transition-colors ${
isActive ? 'border-blue-600 bg-blue-600 text-white' :
isCompleted ? 'border-green-500 bg-green-500 text-white' : 'border-slate-200 text-slate-400 bg-slate-50'
}`}>
{isCompleted ? <CheckCircle2 size={16} /> : idx + 1}
</div>
<span className={`ml-3 text-sm font-medium ${isActive ? 'text-slate-900' : 'text-slate-500'}`}>
{step.label}
</span>
{idx !== steps.length - 1 && (
<div className={`flex-1 h-0.5 mx-4 ${isCompleted ? 'bg-green-500' : 'bg-slate-100'}`}></div>
)}
</div>
</div>
);
})}
</div>
</div>
{/* 演示模式退出按钮 */}
{isPresentationMode && (
<button
onClick={() => setIsPresentationMode(false)}
className="fixed top-4 right-4 z-50 bg-white/90 backdrop-blur text-slate-800 px-4 py-2 rounded-full shadow-lg font-medium text-sm flex items-center hover:bg-white"
>
<EyeOff size={16} className="mr-2"/> 退
</button>
)}
{/* Main Workspace Area */}
<div className="flex-1 bg-white border border-slate-200 rounded-lg shadow-sm overflow-hidden flex flex-col">
{currentStep === 'setup' && (
<SetupStep
setCurrentStep={setCurrentStep}
setInventoryMode={setInventoryMode}
/>
)}
{currentStep === 'inventory' && (
<InventoryStep
inventoryMode={inventoryMode}
setInventoryMode={setInventoryMode}
setCurrentStep={setCurrentStep}
/>
)}
{currentStep === 'context' && (
<ContextStep setCurrentStep={setCurrentStep} />
)}
{currentStep === 'value' && (
<ValueStep
setCurrentStep={setCurrentStep}
selectedScenarios={selectedScenarios}
allScenarios={initialScenarioData}
toggleScenarioSelection={toggleScenarioSelection}
/>
)}
{currentStep === 'delivery' && (
<DeliveryStep selectedScenarios={selectedScenarios} />
)}
</div>
</div>
);
};