Initialize fresh project without Clerk
This commit is contained in:
183
src/client/components/modals/ImportSchemeModal.tsx
Normal file
183
src/client/components/modals/ImportSchemeModal.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal } from '../Modal';
|
||||
import { Select } from '../Select';
|
||||
import { Button } from '../Button';
|
||||
import api from '../../services/api';
|
||||
import { useAuth } from '../../context/useAuth';
|
||||
import { useToast } from '../../hooks/useToast';
|
||||
import type { Project, PaintingScheme } from '../../types';
|
||||
|
||||
interface ImportSchemeModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
targetProjectId: string;
|
||||
isExchangeMode?: boolean;
|
||||
hasInspections?: boolean;
|
||||
}
|
||||
|
||||
export const ImportSchemeModal: React.FC<ImportSchemeModalProps> = ({ isOpen, onClose, onSuccess, targetProjectId, isExchangeMode, hasInspections }) => {
|
||||
const { isGuest } = useAuth();
|
||||
const { showGuestWarning } = useToast();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [schemes, setSchemes] = useState<PaintingScheme[]>([]);
|
||||
|
||||
const [sourceProjectId, setSourceProjectId] = useState('');
|
||||
const [sourceSchemeId, setSourceSchemeId] = useState('');
|
||||
const [shouldReplace, setShouldReplace] = useState(false);
|
||||
|
||||
// Initial state setup
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Default replace to TRUE if in exchange mode and allowed (no inspections)
|
||||
if (isExchangeMode && !hasInspections) {
|
||||
setShouldReplace(true);
|
||||
} else {
|
||||
setShouldReplace(false);
|
||||
}
|
||||
|
||||
api.get('/projects').then(res => {
|
||||
const otherProjects = res.data.filter((p: Project) => p.id !== targetProjectId);
|
||||
setProjects(otherProjects);
|
||||
}).catch(err => console.error("Error loading projects", err));
|
||||
} else {
|
||||
setSourceProjectId('');
|
||||
setSourceSchemeId('');
|
||||
setSchemes([]);
|
||||
setShouldReplace(false);
|
||||
}
|
||||
}, [isOpen, targetProjectId, isExchangeMode, hasInspections]);
|
||||
|
||||
// Fetch schemes when project selected
|
||||
useEffect(() => {
|
||||
if (sourceProjectId) {
|
||||
api.get(`/painting-schemes?projectId=${sourceProjectId}`).then(res => {
|
||||
const projectSchemes = res.data.filter((s: PaintingScheme) => s.projectId === sourceProjectId);
|
||||
setSchemes(projectSchemes);
|
||||
}).catch(err => console.error("Error loading schemes", err));
|
||||
} else {
|
||||
setSchemes([]);
|
||||
}
|
||||
}, [sourceProjectId]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isGuest()) {
|
||||
showGuestWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceSchemeId) return;
|
||||
if (!targetProjectId) {
|
||||
alert("Erro: Projeto de destino não identificado. Tente recarregar a página.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// If Replacing, first delete ALL existing schemes for this project
|
||||
if (shouldReplace && isExchangeMode && !hasInspections) {
|
||||
// 1. Fetch current schemes
|
||||
const currentSchemesRes = await api.get(`/painting-schemes?projectId=${targetProjectId}`);
|
||||
const currentSchemes = currentSchemesRes.data.filter((s: PaintingScheme) => s.projectId === targetProjectId);
|
||||
|
||||
// 2. Delete them
|
||||
await Promise.all(currentSchemes.map((s: PaintingScheme) => api.delete(`/painting-schemes/${s.id}`)));
|
||||
}
|
||||
|
||||
const schemeToClone = schemes.find(s => s.id === sourceSchemeId);
|
||||
if (!schemeToClone) throw new Error("Scheme not found");
|
||||
|
||||
// Clone and remove ID/Project specific fields to create a fresh copy
|
||||
const schemeData = { ...(schemeToClone as any) };
|
||||
delete schemeData.id;
|
||||
delete schemeData.projectId;
|
||||
delete schemeData._id;
|
||||
delete schemeData.__v;
|
||||
delete schemeData.createdAt;
|
||||
delete schemeData.updatedAt;
|
||||
|
||||
await api.post('/painting-schemes', {
|
||||
...schemeData,
|
||||
projectId: targetProjectId,
|
||||
// If replacing, keep original name? User asked to "Exchange". Maybe we don't need "(Cópia)" suffix if strictly exchanging.
|
||||
// But safer to keep distinct unless user renames. Let's keep existing logic or maybe drop suffix if replacing?
|
||||
// Step 1251 prompt implies "Troca Limpa". A clean swap usually implies taking the new scheme AS IS.
|
||||
name: shouldReplace ? schemeData.name : `${schemeData.name} (Cópia)`
|
||||
});
|
||||
|
||||
onSuccess();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error('Error importing scheme', error);
|
||||
alert('Erro ao importar esquema');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={isExchangeMode ? "Trocar / Importar Esquema" : "Importar Esquema de Pintura"}>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<p className="text-sm text-text-muted">Selecione uma obra existente para copiar seu esquema de pintura.</p>
|
||||
|
||||
{isExchangeMode && hasInspections && (
|
||||
<div className="bg-amber-500/10 border border-amber-500/20 p-3 rounded-lg flex gap-3 items-start">
|
||||
<div className="mt-1 text-amber-600 font-bold text-xs uppercase">Atenção</div>
|
||||
<div className="text-xs text-amber-700">
|
||||
Esta obra já possui inspeções ou registros cadastrados. Por segurança, <strong>não é permitido substituir</strong> o esquema atual, apenas adicionar novos itens.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isExchangeMode && !hasInspections && (
|
||||
<div className="bg-surface-soft p-3 rounded-lg border border-border flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="replace-check"
|
||||
className="w-4 h-4 text-primary rounded border focus:ring-primary"
|
||||
checked={shouldReplace}
|
||||
onChange={(e) => setShouldReplace(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="replace-check" className="text-sm font-medium text-text-main cursor-pointer select-none">
|
||||
Substituir todos os esquemas atuais (Limpar Obra)
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Select
|
||||
name="sourceProject"
|
||||
label="Obra/Projeto de Origem"
|
||||
options={projects.map(p => ({ label: p.name, value: p.id }))}
|
||||
value={sourceProjectId}
|
||||
onChange={(e) => setSourceProjectId(e.target.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Select
|
||||
name="sourceScheme"
|
||||
label="Esquema de Pintura"
|
||||
options={schemes.map(s => ({ label: s.name, value: s.id }))}
|
||||
value={sourceSchemeId}
|
||||
onChange={(e) => setSourceSchemeId(e.target.value)}
|
||||
required
|
||||
disabled={!sourceProjectId}
|
||||
/>
|
||||
|
||||
{schemes.length === 0 && sourceProjectId && (
|
||||
<p className="text-xs text-amber-500 font-bold">Esta obra não possui esquemas cadastrados.</p>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2 mt-6">
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={loading}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading || !sourceSchemeId}>
|
||||
{loading ? 'Processando...' : (shouldReplace ? 'Trocar Esquema' : 'Adicionar Cópia')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user