@mytec: iter1.1.1 ready for testing

This commit is contained in:
2026-01-30 23:15:51 +02:00
parent 0fb19476cd
commit b7d008fe26
8 changed files with 422 additions and 11 deletions

View File

@@ -1,7 +1,9 @@
import { useEffect, useState } from 'react';
import { useProjectsStore } from '@/store/projects.ts';
import { useToastStore } from '@/components/ui/Toast.tsx';
import { useDirtyStore } from '@/hooks/useUnsavedChanges.ts';
import Button from '@/components/ui/Button.tsx';
import ConfirmDialog from '@/components/ui/ConfirmDialog.tsx';
import { logger } from '@/utils/logger.ts';
export default function ProjectPanel() {
@@ -12,11 +14,19 @@ export default function ProjectPanel() {
const loadProject = useProjectsStore((s) => s.loadProject);
const deleteProject = useProjectsStore((s) => s.deleteProject);
const addToast = useToastStore((s) => s.addToast);
const isDirty = useDirtyStore((s) => s.isDirty);
const [projectName, setProjectName] = useState('');
const [showSaveForm, setShowSaveForm] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Confirm dialog state
const [confirmAction, setConfirmAction] = useState<{
type: 'load' | 'delete';
id: string;
name: string;
} | null>(null);
useEffect(() => {
loadProjects();
}, [loadProjects]);
@@ -30,6 +40,7 @@ export default function ProjectPanel() {
try {
await saveProject(name);
useDirtyStore.getState().markClean();
addToast(`Project "${name}" saved`, 'success');
setProjectName('');
setShowSaveForm(false);
@@ -39,11 +50,20 @@ export default function ProjectPanel() {
}
};
const handleLoad = async (id: string, name: string) => {
const handleLoadRequest = (id: string, name: string) => {
if (isDirty) {
setConfirmAction({ type: 'load', id, name });
} else {
executeLoad(id, name);
}
};
const executeLoad = async (id: string, name: string) => {
setIsLoading(true);
try {
const project = await loadProject(id);
if (project) {
useDirtyStore.getState().markClean();
addToast(`Loaded project "${name}" (${project.sites.length} sites)`, 'success');
} else {
addToast('Project not found', 'error');
@@ -56,7 +76,11 @@ export default function ProjectPanel() {
}
};
const handleDelete = async (id: string, name: string) => {
const handleDeleteRequest = (id: string, name: string) => {
setConfirmAction({ type: 'delete', id, name });
};
const executeDelete = async (id: string, name: string) => {
try {
await deleteProject(id);
addToast(`Project "${name}" deleted`, 'info');
@@ -66,6 +90,18 @@ export default function ProjectPanel() {
}
};
const handleConfirm = async () => {
if (!confirmAction) return;
const { type, id, name } = confirmAction;
setConfirmAction(null);
if (type === 'load') {
await executeLoad(id, name);
} else {
await executeDelete(id, name);
}
};
const formatDate = (timestamp: number) => {
return new Date(timestamp).toLocaleDateString(undefined, {
month: 'short',
@@ -142,7 +178,7 @@ export default function ProjectPanel() {
</div>
<div className="flex gap-1 flex-shrink-0">
<button
onClick={() => handleLoad(project.id, project.name)}
onClick={() => handleLoadRequest(project.id, project.name)}
disabled={isLoading}
className="px-2 py-1 text-xs text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded
min-w-[40px] min-h-[32px] flex items-center justify-center disabled:opacity-50"
@@ -150,7 +186,7 @@ export default function ProjectPanel() {
Load
</button>
<button
onClick={() => handleDelete(project.id, project.name)}
onClick={() => handleDeleteRequest(project.id, project.name)}
className="px-2 py-1 text-xs text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded
min-w-[32px] min-h-[32px] flex items-center justify-center"
>
@@ -161,6 +197,27 @@ export default function ProjectPanel() {
))}
</div>
)}
{/* Confirmation dialog */}
{confirmAction && (
<ConfirmDialog
title={
confirmAction.type === 'delete'
? 'Delete Project?'
: 'Unsaved Changes'
}
message={
confirmAction.type === 'delete'
? `Delete project "${confirmAction.name}"? This cannot be undone.`
: `You have unsaved changes. Load project "${confirmAction.name}" anyway?`
}
confirmLabel={confirmAction.type === 'delete' ? 'Delete' : 'Load'}
cancelLabel="Cancel"
variant={confirmAction.type === 'delete' ? 'danger' : 'warning'}
onConfirm={handleConfirm}
onCancel={() => setConfirmAction(null)}
/>
)}
</div>
);
}