Initial commit DBMaker - Oficiais e Funcionando

This commit is contained in:
Marcos
2026-03-22 17:12:45 -03:00
commit 9cee4943f8
144 changed files with 31465 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
import Input from '@/components/common/Input'
interface TemplateEditorProps {
tipo: string
config: Record<string, any>
onChange: (config: Record<string, any>) => void
}
export default function TemplateEditor({ tipo, config, onChange }: TemplateEditorProps) {
const handleColorChange = (key: string, value: string) => {
onChange({ ...config, [key]: value })
}
const handleTextChange = (key: string, value: string) => {
onChange({ ...config, [key]: value })
}
const renderEditorByType = () => {
switch (tipo) {
case 'capa':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Primária
</label>
<input
type="color"
value={config.corPrimaria || '#1a365d'}
onChange={(e) => handleColorChange('corPrimaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Secundária
</label>
<input
type="color"
value={config.corSecundaria || '#2b6cb0'}
onChange={(e) => handleColorChange('corSecundaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer"
/>
</div>
<Input
label="Título Principal"
value={config.titulo || ''}
onChange={(e) => handleTextChange('titulo', e.target.value)}
placeholder="Ex: BUZIOS 7 PRODUCTION SYSTEM"
/>
<Input
label="Subtítulo"
value={config.subtitulo || ''}
onChange={(e) => handleTextChange('subtitulo', e.target.value)}
placeholder="Ex: AR HEAD FABRICATION"
/>
<Input
label="Cliente"
value={config.cliente || ''}
onChange={(e) => handleTextChange('cliente', e.target.value)}
placeholder="Ex: SAIPEM"
/>
<Input
label="Número do Documento"
value={config.numeroDocumento || ''}
onChange={(e) => handleTextChange('numeroDocumento', e.target.value)}
placeholder="Ex: DB-B97-01_S1_VENDOR_DATABOOK"
/>
<Input
label="Contrato"
value={config.contrato || ''}
onChange={(e) => handleTextChange('contrato', e.target.value)}
placeholder="Ex: OC 1472739"
/>
<Input
label="Fornecedor"
value={config.fornecedor || ''}
onChange={(e) => handleTextChange('fornecedor', e.target.value)}
placeholder="Ex: ENGEMETAL"
/>
</div>
)
case 'indice':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor do Título
</label>
<input
type="color"
value={config.corTitulo || '#1a365d'}
onChange={(e) => handleColorChange('corTitulo', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor da Linha Divisória
</label>
<input
type="color"
value={config.corLinha || '#2b6cb0'}
onChange={(e) => handleColorChange('corLinha', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={config.bilingue || false}
onChange={(e) => handleColorChange('bilingue', e.target.checked ? 'true' : 'false')}
className="rounded border-gray-300 dark:border-gray-600"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Bilíngue (PT/EN)</span>
</label>
</div>
<Input
label="Título do Índice"
value={config.titulo || 'ÍNDICE / TABLE OF CONTENTS'}
onChange={(e) => handleTextChange('titulo', e.target.value)}
placeholder="Título do índice"
/>
</div>
)
case 'divisora':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Estilo da Divisora
</label>
<select
value={config.estilo || 'minimalista'}
onChange={(e) => handleColorChange('estilo', e.target.value)}
className="input-field"
>
<option value="minimalista">Minimalista</option>
<option value="lateral">Lateral</option>
<option value="corporativa">Corporativa</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Primária
</label>
<input
type="color"
value={config.corPrimaria || '#1a365d'}
onChange={(e) => handleColorChange('corPrimaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Secundária
</label>
<input
type="color"
value={config.corSecundaria || '#2b6cb0'}
onChange={(e) => handleColorChange('corSecundaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={config.bilingue || false}
onChange={(e) => handleColorChange('bilingue', e.target.checked ? 'true' : 'false')}
className="rounded border-gray-300 dark:border-gray-600"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Bilíngue (PT/EN)</span>
</label>
</div>
<Input
label="Ícone da Seção"
value={config.icone || '📑'}
onChange={(e) => handleTextChange('icone', e.target.value)}
placeholder="Ex: 📑, 🔩, ⚡"
maxLength={2}
/>
</div>
)
case 'cabecalho':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor da Borda
</label>
<input
type="color"
value={config.corBorda || '#2b6cb0'}
onChange={(e) => handleColorChange('corBorda', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<Input
label="Altura (px)"
type="number"
value={config.altura || 60}
onChange={(e) => handleTextChange('altura', e.target.value)}
placeholder="60"
/>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Estilo
</label>
<select
value={config.estilo || 'simples'}
onChange={(e) => handleColorChange('estilo', e.target.value)}
className="input-field"
>
<option value="simples">Simples</option>
<option value="completo">Completo com Logo</option>
<option value="minimalista">Minimalista</option>
</select>
</div>
</div>
)
case 'rodape':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor da Borda
</label>
<input
type="color"
value={config.corBorda || '#cbd5e0'}
onChange={(e) => handleColorChange('corBorda', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<Input
label="Altura (px)"
type="number"
value={config.altura || 40}
onChange={(e) => handleTextChange('altura', e.target.value)}
placeholder="40"
/>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Estilo
</label>
<select
value={config.estilo || 'simples'}
onChange={(e) => handleColorChange('estilo', e.target.value)}
className="input-field"
>
<option value="simples">Simples</option>
<option value="completo">Completo</option>
<option value="minimalista">Minimalista</option>
</select>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={config.mostrarPagina || true}
onChange={(e) => handleColorChange('mostrarPagina', e.target.checked ? 'true' : 'false')}
className="rounded border-gray-300 dark:border-gray-600"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Mostrar Número da Página</span>
</label>
</div>
</div>
)
case 'guia_estilo':
return (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Primária
</label>
<input
type="color"
value={config.corPrimaria || '#1a365d'}
onChange={(e) => handleColorChange('corPrimaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor Secundária
</label>
<input
type="color"
value={config.corSecundaria || '#2b6cb0'}
onChange={(e) => handleColorChange('corSecundaria', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Cor de Destaque
</label>
<input
type="color"
value={config.corDestaque || '#4299e1'}
onChange={(e) => handleColorChange('corDestaque', e.target.value)}
className="w-full h-10 rounded border border-gray-300 dark:border-gray-600 cursor-pointer bg-white dark:bg-gray-800"
/>
</div>
<Input
label="Fonte Principal"
value={config.fontePrincipal || 'Roboto'}
onChange={(e) => handleTextChange('fontePrincipal', e.target.value)}
placeholder="Ex: Roboto, Arial"
/>
<Input
label="Fonte Secundária"
value={config.fonteSecundaria || 'Open Sans'}
onChange={(e) => handleTextChange('fonteSecundaria', e.target.value)}
placeholder="Ex: Open Sans, Helvetica"
/>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={config.incluirPaleta || true}
onChange={(e) => handleColorChange('incluirPaleta', e.target.checked ? 'true' : 'false')}
className="rounded border-gray-300 dark:border-gray-600"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Incluir Paleta de Cores</span>
</label>
</div>
<div>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={config.incluirTipografia || true}
onChange={(e) => handleColorChange('incluirTipografia', e.target.checked ? 'true' : 'false')}
className="rounded border-gray-300 dark:border-gray-600"
/>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Incluir Tipografia</span>
</label>
</div>
</div>
)
default:
return <p className="text-gray-500 dark:text-gray-400">Selecione um tipo de template</p>
}
}
return (
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 space-y-4">
<h3 className="font-semibold text-gray-900 dark:text-gray-100">Configurações do Template</h3>
{renderEditorByType()}
</div>
)
}

View File

@@ -0,0 +1,394 @@
import { useState, useRef } from 'react'
import { ZoomIn, ZoomOut, Maximize2 } from 'lucide-react'
interface TemplatePreviewProps {
tipo: string
config: Record<string, any>
}
export default function TemplatePreview({ tipo, config }: TemplatePreviewProps) {
const [zoom, setZoom] = useState(30)
const [isPanning, setIsPanning] = useState(false)
const [panStart, setPanStart] = useState({ x: 0, y: 0 })
const scrollContainerRef = useRef<HTMLDivElement>(null)
const handleZoomIn = () => setZoom(prev => Math.min(prev + 10, 200))
const handleZoomOut = () => setZoom(prev => Math.max(prev - 10, 30))
const handleResetZoom = () => setZoom(100)
const handleMouseDown = (e: React.MouseEvent) => {
if (!scrollContainerRef.current) return
setIsPanning(true)
setPanStart({
x: e.clientX + scrollContainerRef.current.scrollLeft,
y: e.clientY + scrollContainerRef.current.scrollTop,
})
}
const handleMouseMove = (e: React.MouseEvent) => {
if (!isPanning || !scrollContainerRef.current) return
scrollContainerRef.current.scrollLeft = panStart.x - e.clientX
scrollContainerRef.current.scrollTop = panStart.y - e.clientY
}
const handleMouseUp = () => {
setIsPanning(false)
}
const renderPreview = () => {
const corPrimaria = config.corPrimaria || '#1a365d'
const corSecundaria = config.corSecundaria || '#2b6cb0'
switch (tipo) {
case 'capa':
return (
<div
className="bg-white shadow-2xl flex flex-col justify-between p-8"
style={{
width: '210mm',
height: '297mm',
background: `linear-gradient(135deg, rgba(${parseInt(corPrimaria.slice(1, 3), 16)}, ${parseInt(corPrimaria.slice(3, 5), 16)}, ${parseInt(corPrimaria.slice(5, 7), 16)}, 0.05), rgba(${parseInt(corSecundaria.slice(1, 3), 16)}, ${parseInt(corSecundaria.slice(3, 5), 16)}, ${parseInt(corSecundaria.slice(5, 7), 16)}, 0.05))`,
}}
>
<div className="text-center">
<div className="w-32 h-16 bg-gray-300 rounded mx-auto mb-8 flex items-center justify-center text-gray-500 text-sm">
Logo Cliente
</div>
</div>
<div className="text-center flex-1 flex flex-col justify-center">
<h1
className="text-4xl font-bold mb-4 line-clamp-3"
style={{ color: corPrimaria }}
>
{config.titulo || 'TÍTULO DO PROJETO'}
</h1>
<h2 className="text-2xl text-gray-700 mb-6">
{config.subtitulo || 'Subtítulo do Projeto'}
</h2>
<div
className="w-24 h-1 mx-auto mb-6"
style={{ background: corSecundaria }}
/>
<div className="text-lg text-gray-600">
<p className="font-semibold" style={{ color: corSecundaria }}>
{config.numeroDocumento || 'DB-XXXX-XX_SX_VENDOR_DATABOOK'}
</p>
<p>Contrato: {config.contrato || 'OC XXXXXXX'}</p>
<p>Cliente: {config.cliente || 'CLIENTE'}</p>
</div>
</div>
<div className="text-center">
<div className="w-24 h-12 bg-gray-300 rounded mx-auto flex items-center justify-center text-gray-500 text-xs">
Logo {config.fornecedor || 'Fornecedor'}
</div>
</div>
</div>
)
case 'indice':
return (
<div
className="bg-white shadow-2xl overflow-y-auto p-8"
style={{
width: '210mm',
height: '297mm',
}}
>
<h1
className="text-3xl font-bold text-center mb-2"
style={{ color: corPrimaria }}
>
ÍNDICE
</h1>
{config.bilingue && (
<p className="text-center text-gray-600 mb-4">TABLE OF CONTENTS</p>
)}
<div
className="w-full h-1 mb-6"
style={{ background: corSecundaria }}
/>
<div className="space-y-2 text-sm">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="flex items-center justify-between">
<span className="font-semibold" style={{ color: corSecundaria }}>
{i}
</span>
<span className="flex-1 mx-2 border-b border-dotted border-gray-400" />
<span className="font-semibold">{i * 5}</span>
</div>
))}
</div>
</div>
)
case 'divisora':
const estilo = config.estilo || 'minimalista'
if (estilo === 'lateral') {
return (
<div
className="bg-white shadow-2xl flex overflow-hidden"
style={{
width: '210mm',
height: '297mm',
}}
>
<div
className="w-20 flex items-center justify-center text-5xl font-bold text-white"
style={{ background: `linear-gradient(180deg, ${corPrimaria}, ${corSecundaria})` }}
>
2
</div>
<div className="flex-1 p-8 flex flex-col justify-center">
<h1 className="text-4xl font-bold mb-2" style={{ color: corPrimaria }}>
Materiais
</h1>
{config.bilingue && (
<h2 className="text-2xl text-gray-600 italic mb-6">Materials</h2>
)}
<div className="bg-gray-100 p-4 rounded text-sm text-gray-700">
<p>
<strong>Projeto:</strong> Projeto Exemplo
</p>
<p>
<strong>Cliente:</strong> Cliente Exemplo
</p>
</div>
</div>
</div>
)
}
return (
<div
className="bg-white shadow-2xl flex flex-col items-center justify-center p-8 relative overflow-hidden"
style={{
width: '210mm',
height: '297mm',
}}
>
<div
className="absolute text-9xl font-bold opacity-5"
style={{ color: corPrimaria }}
>
2
</div>
<div className="relative z-10 text-center">
<p className="text-2xl mb-4" style={{ color: corSecundaria }}>
{config.icone || '📑'} Seção 2
</p>
<h1 className="text-5xl font-bold mb-4" style={{ color: corPrimaria }}>
Materiais
</h1>
{config.bilingue && (
<h2 className="text-2xl text-gray-600 italic mb-6">Materials</h2>
)}
<div
className="w-32 h-1 mx-auto"
style={{ background: corSecundaria }}
/>
</div>
</div>
)
case 'cabecalho':
return (
<div
className="bg-white shadow-2xl overflow-hidden"
style={{
width: '210mm',
}}
>
<div
className="flex items-center justify-between p-4"
style={{ borderBottom: `2px solid ${corSecundaria}` }}
>
<div className="w-16 h-8 bg-gray-300 rounded flex items-center justify-center text-xs text-gray-500">
Logo
</div>
<span className="text-sm font-semibold text-gray-700">
Projeto Exemplo
</span>
<span className="text-xs text-gray-500 font-mono">
DB-XXXX-XX_SX_VENDOR_DATABOOK
</span>
</div>
</div>
)
case 'rodape':
return (
<div
className="bg-white shadow-2xl overflow-hidden"
style={{
width: '210mm',
}}
>
<div
className="flex items-center justify-between p-3 text-xs text-gray-600"
style={{ borderTop: `1px solid ${config.corBorda || '#cbd5e0'}` }}
>
<span>Rev. 01 | 2024</span>
<span className="font-bold text-lg" style={{ color: corPrimaria }}>
12
</span>
<span>Fornecedor</span>
</div>
</div>
)
case 'guia_estilo':
return (
<div
className="bg-white shadow-2xl p-8 overflow-y-auto"
style={{
width: '210mm',
height: '297mm',
}}
>
<h1
className="text-3xl font-bold text-center mb-8"
style={{ color: corPrimaria }}
>
Guia de Estilo
</h1>
<div className="mb-8">
<h2 className="text-xl font-bold mb-4" style={{ color: corPrimaria }}>
Paleta de Cores
</h2>
<div className="flex gap-4">
<div className="flex-1">
<div
className="w-full h-16 rounded mb-2"
style={{ backgroundColor: corPrimaria }}
/>
<p className="text-xs text-gray-600">Primária</p>
</div>
<div className="flex-1">
<div
className="w-full h-16 rounded mb-2"
style={{ backgroundColor: corSecundaria }}
/>
<p className="text-xs text-gray-600">Secundária</p>
</div>
<div className="flex-1">
<div
className="w-full h-16 rounded mb-2"
style={{ backgroundColor: config.corDestaque || '#4299e1' }}
/>
<p className="text-xs text-gray-600">Destaque</p>
</div>
</div>
</div>
<div>
<h2 className="text-xl font-bold mb-4" style={{ color: corPrimaria }}>
Tipografia
</h2>
<div className="space-y-3">
<div style={{ fontFamily: config.fontePrincipal || 'Roboto' }}>
<p className="text-2xl font-bold">Título Principal</p>
<p className="text-xs text-gray-600">
{config.fontePrincipal || 'Roboto'}
</p>
</div>
<div style={{ fontFamily: config.fonteSecundaria || 'Open Sans' }}>
<p className="text-base">Corpo de Texto</p>
<p className="text-xs text-gray-600">
{config.fonteSecundaria || 'Open Sans'}
</p>
</div>
</div>
</div>
</div>
)
default:
return (
<div
className="bg-white shadow-2xl flex items-center justify-center"
style={{
width: '210mm',
height: '297mm',
}}
>
<p className="text-gray-500">Preview não disponível</p>
</div>
)
}
}
return (
<div className="flex flex-col h-full space-y-2">
{/* Controles de Zoom */}
<div className="flex items-center justify-between bg-gray-100 dark:bg-gray-700 rounded-lg p-2 flex-shrink-0">
<div className="flex items-center gap-1">
<button
onClick={handleZoomOut}
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded transition-colors text-gray-700 dark:text-gray-300"
title="Diminuir zoom"
>
<ZoomOut size={16} />
</button>
<span className="text-xs font-medium w-10 text-center text-gray-700 dark:text-gray-300">{zoom}%</span>
<button
onClick={handleZoomIn}
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded transition-colors text-gray-700 dark:text-gray-300"
title="Aumentar zoom"
>
<ZoomIn size={16} />
</button>
<div className="w-px h-5 bg-gray-300 dark:bg-gray-600 mx-1" />
<button
onClick={handleResetZoom}
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded transition-colors flex items-center gap-1 text-gray-700 dark:text-gray-300"
title="Resetar zoom"
>
<Maximize2 size={16} />
<span className="text-xs">100%</span>
</button>
</div>
<span className="text-xs text-gray-600 dark:text-gray-400">
A4 (210mm × 297mm)
</span>
</div>
{/* Preview Container - Pan para navegar */}
<div
ref={scrollContainerRef}
className="flex-1 bg-gray-200 rounded-lg overflow-auto cursor-grab active:cursor-grabbing"
style={{ minHeight: 0 }}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
<div
className="flex items-center justify-center p-3"
style={{
minHeight: '100%',
minWidth: '100%',
}}
>
<div
style={{
width: `${210 * zoom / 100}mm`,
height: `${297 * zoom / 100}mm`,
transform: `scale(${zoom / 100})`,
transformOrigin: 'center',
transition: 'transform 0.2s ease-out',
userSelect: 'none',
}}
>
{renderPreview()}
</div>
</div>
</div>
</div>
)
}