히스토리 패널 UI 작업 완료
- T-008 태스크 완료: 히스토리 패널 UI 마크업 구현 (슬라이드 인) - 히스토리, UNDO, 전송하기 버튼을 세로로 균등 간격 배치 - Tailwind CSS v3/v4 버전 충돌 문제 해결 - v4 패키지 완전 제거 (@tailwindcss/postcss 등) - PostCSS 설정을 v3 방식으로 수정 - CSS 파일에서 수동 클래스 정의 제거 - Vite 캐시 완전 삭제로 설정 변경 반영 - 히스토리 패널 기능 개선 - 우측 슬라이드 인 애니메이션 - 파일 업로드 시에만 표시 - 상태별 아이콘과 시간순 로그 리스트 - 재적용 및 전체 삭제 기능 - 새로운 rule 파일 생성: tailwind-css-management.mdc
This commit is contained in:
@@ -15,10 +15,12 @@ import "@univerjs/presets/lib/styles/preset-sheets-core.css";
|
||||
import { cn } from "../../lib/utils";
|
||||
import LuckyExcel from "@zwight/luckyexcel";
|
||||
import PromptInput from "./PromptInput";
|
||||
import HistoryPanel from "../ui/historyPanel";
|
||||
import { useAppStore } from "../../stores/useAppStore";
|
||||
import { rangeToAddress } from "../../utils/cellUtils";
|
||||
import { CellSelectionHandler } from "../../utils/cellSelectionHandler";
|
||||
import { aiProcessor } from "../../utils/aiProcessor";
|
||||
import type { HistoryEntry } from "../../types/ai";
|
||||
|
||||
// 전역 고유 키 생성
|
||||
const GLOBAL_UNIVER_KEY = "__GLOBAL_UNIVER_INSTANCE__";
|
||||
@@ -337,6 +339,52 @@ const TestSheetViewer: React.FC = () => {
|
||||
// CellSelectionHandler 인스턴스 생성
|
||||
const cellSelectionHandler = useRef(new CellSelectionHandler());
|
||||
|
||||
// 히스토리 관련 상태 추가
|
||||
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
|
||||
const [history, setHistory] = useState<HistoryEntry[]>([]);
|
||||
|
||||
// 히스토리 관련 핸들러
|
||||
const handleHistoryToggle = () => {
|
||||
console.log("🔄 히스토리 토글:", !isHistoryOpen);
|
||||
setIsHistoryOpen(!isHistoryOpen);
|
||||
};
|
||||
|
||||
const handleHistoryClear = () => {
|
||||
if (window.confirm("모든 히스토리를 삭제하시겠습니까?")) {
|
||||
setHistory([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleHistoryReapply = (entry: HistoryEntry) => {
|
||||
// 히스토리 항목 재적용 로직
|
||||
setPrompt(entry.prompt);
|
||||
console.log("🔄 히스토리 재적용:", entry);
|
||||
// TODO: 실제 액션 재실행 로직 구현
|
||||
};
|
||||
|
||||
// 새 히스토리 항목 추가 함수
|
||||
const addHistoryEntry = (
|
||||
prompt: string,
|
||||
range: string,
|
||||
sheetName: string,
|
||||
actions: any[],
|
||||
status: "success" | "error" | "pending",
|
||||
error?: string,
|
||||
) => {
|
||||
const newEntry: HistoryEntry = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
timestamp: new Date(),
|
||||
prompt,
|
||||
range,
|
||||
sheetName,
|
||||
actions,
|
||||
status,
|
||||
error,
|
||||
};
|
||||
|
||||
setHistory((prev) => [newEntry, ...prev]); // 최신 항목을 맨 위에
|
||||
};
|
||||
|
||||
// Univer 초기화 함수 (Presets 기반)
|
||||
const initializeUniver = useCallback(
|
||||
async (workbookData?: any) => {
|
||||
@@ -718,7 +766,23 @@ const TestSheetViewer: React.FC = () => {
|
||||
<div style={{ height: "1rem" }} />
|
||||
|
||||
{/* 프롬프트 입력창 - Univer 하단에 이어서 */}
|
||||
<PromptInput value={prompt} onChange={(e) => setPrompt(e.target.value)} />
|
||||
<PromptInput
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
onHistoryToggle={handleHistoryToggle}
|
||||
historyCount={history.length}
|
||||
/>
|
||||
|
||||
{/* 히스토리 패널 - 파일이 업로드된 후에만 표시 */}
|
||||
{!showUploadOverlay && (
|
||||
<HistoryPanel
|
||||
isOpen={isHistoryOpen}
|
||||
onClose={() => setIsHistoryOpen(false)}
|
||||
history={history}
|
||||
onReapply={handleHistoryReapply}
|
||||
onClear={handleHistoryClear}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 파일 업로드 오버레이 - 레이어 분리 */}
|
||||
{showUploadOverlay && (
|
||||
|
||||
@@ -8,6 +8,8 @@ interface PromptInputProps {
|
||||
onExecute?: () => void;
|
||||
disabled?: boolean;
|
||||
maxLength?: number;
|
||||
onHistoryToggle?: () => void;
|
||||
historyCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,8 @@ const PromptInput: React.FC<PromptInputProps> = ({
|
||||
onExecute,
|
||||
disabled = true,
|
||||
maxLength = 500,
|
||||
onHistoryToggle,
|
||||
historyCount,
|
||||
}) => {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [showCellInsertFeedback, setShowCellInsertFeedback] = useState(false);
|
||||
@@ -182,18 +186,53 @@ const PromptInput: React.FC<PromptInputProps> = ({
|
||||
rows={5}
|
||||
/>
|
||||
<div style={{ width: "1rem" }} />
|
||||
<button
|
||||
className="ml-2 px-6 py-2 rounded-lg text-white font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: isProcessing
|
||||
? "#6b7280"
|
||||
: "linear-gradient(90deg, #a18fff 0%, #6f6fff 100%)",
|
||||
}}
|
||||
onClick={handleExecute}
|
||||
disabled={isProcessing || !value.trim()}
|
||||
>
|
||||
{isProcessing ? "처리 중..." : "전송하기"}
|
||||
</button>
|
||||
|
||||
{/* 버튼들을 세로로 배치 */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* 히스토리 버튼 - 맨 위 */}
|
||||
{onHistoryToggle && (
|
||||
<button
|
||||
className="px-4 py-2 rounded-lg text-gray-600 border border-gray-300 hover:bg-gray-50 font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
onClick={onHistoryToggle}
|
||||
disabled={isProcessing}
|
||||
aria-label="작업 히스토리 보기"
|
||||
>
|
||||
📝
|
||||
{historyCount !== undefined && historyCount > 0 && (
|
||||
<span className="text-xs bg-blue-500 text-white rounded-full px-2 py-0.5 min-w-[20px] h-5 flex items-center justify-center">
|
||||
{historyCount > 99 ? "99+" : historyCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* UNDO 버튼 - 중간 */}
|
||||
<button
|
||||
className="px-4 py-2 rounded-lg text-gray-600 border border-gray-300 hover:bg-gray-50 font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
onClick={() => {
|
||||
// TODO: UNDO 기능 구현
|
||||
console.log("🔄 UNDO 버튼 클릭");
|
||||
}}
|
||||
disabled={isProcessing}
|
||||
aria-label="실행 취소"
|
||||
>
|
||||
↶
|
||||
</button>
|
||||
|
||||
{/* 전송하기 버튼 - 맨 아래 */}
|
||||
<button
|
||||
className="px-6 py-2 rounded-lg text-white font-semibold text-base shadow transition disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: isProcessing
|
||||
? "#6b7280"
|
||||
: "linear-gradient(90deg, #a18fff 0%, #6f6fff 100%)",
|
||||
}}
|
||||
onClick={handleExecute}
|
||||
disabled={isProcessing || !value.trim()}
|
||||
>
|
||||
{isProcessing ? "처리 중..." : "전송하기"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-3xl flex justify-between items-center mt-1 px-1">
|
||||
<span className="text-xs text-gray-500">
|
||||
|
||||
Reference in New Issue
Block a user