Files
server/resources/views/admin/orders/create.php
2026-03-17 11:20:17 -03:00

225 lines
13 KiB
PHP

<?php
$hasError = isset($_SESSION['flash_error']);
$errorMsg = $_SESSION['flash_error'] ?? '';
if ($hasError) {
unset($_SESSION['flash_error']); // Prevent toast from showing
}
?>
<div class="max-w-3xl mx-auto bg-white rounded-xl shadow-sm border border-gray-100 p-6" x-data='orderForm(<?= $hasError ? 'true' : 'false' ?>, <?= htmlspecialchars(json_encode($errorMsg), ENT_QUOTES, 'UTF-8') ?>)'>
<!-- Modal de Erro -->
<div x-show="showModal" class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true" x-cloak>
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<!-- Background overlay -->
<div x-show="showModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" @click="showModal = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<!-- Painel do Modal -->
<div x-show="showModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="inline-block align-bottom bg-white rounded-xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg leading-6 font-bold text-gray-900" id="modal-title" x-text="modalTitle"></h3>
<button @click="showModal = false" type="button" class="text-gray-400 hover:text-gray-500 focus:outline-none">
<span class="sr-only">Fechar</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="mt-2">
<p class="text-sm text-gray-600" x-text="modalMessage"></p>
</div>
<div class="mt-6 flex justify-end">
<button type="button" @click="showModal = false" class="inline-flex justify-center w-full rounded-lg border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-200 sm:w-auto sm:text-sm transition-colors">
Fechar
</button>
</div>
</div>
</div>
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-6">Nova Ordem Judicial</h3>
<!-- Quill Styles -->
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<form action="/admin/orders/store" method="POST" enctype="multipart/form-data" class="space-y-6" id="orderForm" @submit="validateForm($event)">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Título / Identificação da Ordem</label>
<input type="text" name="title" required placeholder="Ex: Processo nº 123456 - Bloqueio Jogos"
oninvalid="this.setCustomValidity('Por favor, preencha o título da ordem.')"
oninput="this.setCustomValidity('')"
class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tipo da Ordem</label>
<select name="type" required oninvalid="this.setCustomValidity('Por favor, selecione o tipo da ordem.')"
oninput="this.setCustomValidity('')"
class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none">
<option value="block">Bloqueio</option>
<option value="unblock">Desbloqueio</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Data de Recebimento</label>
<input type="date" name="received_at"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
required oninvalid="this.setCustomValidity('Por favor, informe a data de recebimento.')"
oninput="this.setCustomValidity('')">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Conteúdo do Email / Descrição</label>
<div id="editor" class="bg-white" style="height: 200px;"></div>
<input type="hidden" name="content" id="content">
</div>
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Arquivo de Domínios (.csv, .txt)</label>
<div x-data="{ fileName: '' }" class="mt-1">
<label
class="flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer hover:border-primary-500 transition-colors bg-white">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none"
viewBox="0 0 48 48" aria-hidden="true">
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="flex text-sm text-gray-600 justify-center">
<span class="font-medium text-primary-600 hover:text-primary-500">Upload um
arquivo</span>
<p class="pl-1">ou arraste e solte</p>
</div>
<p class="text-xs text-gray-500">CSV ou TXT até 10MB</p>
<p class="text-sm font-semibold text-gray-800 mt-2"
x-text="fileName ? 'Selecionado: ' + fileName : ''"></p>
</div>
<input type="file" name="csv_file" class="sr-only" accept=".csv,.txt"
@change="fileName = $event.target.files[0].name" required
oninvalid="this.setCustomValidity('Por favor, selecione um arquivo CSV ou TXT.')"
oninput="this.setCustomValidity('')">
</label>
</div>
</div>
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">
Anexos <span class="text-gray-400 font-normal">(PDF, imagens, documentos — opcional)</span>
</label>
<p class="text-xs text-gray-500 mb-2">Arquivos da ordem judicial (decisão, ofícios, etc.). Tipos aceitos: PDF, PNG, JPG, GIF, DOC, DOCX, XLS, XLSX, TXT — máx. 20 MB por arquivo.</p>
<div x-data="{ attachNames: [] }" class="mt-1">
<label class="flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer hover:border-primary-500 transition-colors bg-white">
<div class="space-y-1 text-center w-full">
<svg class="mx-auto h-10 w-10 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13" />
</svg>
<div class="flex text-sm text-gray-600 justify-center">
<span class="font-medium text-primary-600 hover:text-primary-500">Selecionar arquivos</span>
<p class="pl-1">ou arraste e solte</p>
</div>
<template x-if="attachNames.length > 0">
<ul class="text-xs text-gray-700 mt-2 text-left list-disc list-inside">
<template x-for="name in attachNames" :key="name">
<li x-text="name"></li>
</template>
</ul>
</template>
<p x-show="attachNames.length === 0" class="text-xs text-gray-400 mt-1">Nenhum arquivo selecionado</p>
</div>
<input type="file" name="attachments[]" class="sr-only"
accept=".pdf,.png,.jpg,.jpeg,.gif,.doc,.docx,.xls,.xlsx,.txt"
multiple
@change="attachNames = Array.from($event.target.files).map(f => f.name)">
</label>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-100">
<a href="/admin/orders"
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors">Cancelar</a>
<button type="submit"
class="px-4 py-2 text-white bg-primary-600 rounded-lg hover:bg-primary-700 transition-colors">Salvar e
Processar</button>
</div>
</form>
</div>
<!-- Quill Script -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<script>
var quill = new Quill('#editor', {
theme: 'snow',
placeholder: 'Cole o conteúdo do email ou descreva a ordem aqui...',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'sub' }, { 'script': 'super' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['clean'],
['link']
]
}
});
document.addEventListener('alpine:init', () => {
Alpine.data('orderForm', (initialShow, initialMsg) => ({
showModal: initialShow,
modalTitle: 'Aviso',
modalMessage: initialMsg || '',
validateForm(e) {
let totalSize = 0;
let maxSize = 20 * 1024 * 1024; // 20 MB
let attachments = document.querySelector('input[name=\'attachments[]\']').files;
for(let i = 0; i < attachments.length; i++) {
if (attachments[i].size > maxSize) {
this.showError('Arquivo Muito Grande', 'O anexo \'' + attachments[i].name + '\' excede o limite permitido de 20 MB.');
e.preventDefault();
return false;
}
totalSize += attachments[i].size;
}
let csvFile = document.querySelector('input[name=\'csv_file\']').files[0];
if (csvFile) {
totalSize += csvFile.size;
}
if (totalSize > 40 * 1024 * 1024) { // 40 MB limite de segurança frontend
this.showError('Tamanho Total Excedido', 'O tamanho total de todos os arquivos excede o limite de submissão do servidor.');
e.preventDefault();
return false;
}
// Popula o campo oculto do Quill
document.querySelector('input[name=content]').value = window.quill.root.innerHTML;
return true;
},
showError(title, message) {
this.modalTitle = title;
this.modalMessage = message;
this.showModal = true;
}
}));
});
</script>