new functionality: Add project coding configuration feature for projects
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
2025-12-09 23:02:35 +01:00
parent 7b00887372
commit e42ce8b092
13 changed files with 1169 additions and 28 deletions

View File

@@ -134,6 +134,45 @@
{{ __('Create User') }}
</flux:navlist.item>
</flux:navlist.group>
<flux:separator />
<!-- Sección de Empresas -->
<flux:navlist.group :heading="__('Empresas')" expandable>
<flux:navlist.item
icon="building-office-2"
:href="route('companies.index')"
wire:navigate
>
{{ __('List companies') }}
</flux:navlist.item>
<flux:navlist.item
icon="building-office"
:href="route('companies.create')"
wire:navigate
>
{{ __('Create new company') }}
</flux:navlist.item>
</flux:navlist.group>
<flux:separator />
<flux:navlist.group :heading="__('Projects')" expandable>
<flux:navlist.item
icon="folder"
:href="route('projects.index')"
wire:navigate
>
{{ __('List Projects') }}
</flux:navlist.item>
<flux:navlist.item
icon="plus"
:href="route('projects.create')"
wire:navigate
>
{{ __('Create Project') }}
</flux:navlist.item>
</flux:navlist.group>
</flux:navlist>
@endpush

View File

@@ -1,20 +1,42 @@
<div class="max-w-full mx-auto p-6">
<div class="max-w-full">
<!-- Header con contador y botón de agregar -->
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold text-gray-800">
Codificación de los documentos del proyecto
</h2>
<div class="flex content-start justify-between mb-6">
<div class="flex space-x-6 content-start">
<h2 class="text-2xl font-bold text-gray-800">
Codificación de los documentos del proyecto
</h2>
</div>
<button
type="button"
wire:click="addComponent"
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors flex items-center space-x-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
<span>Agregar Componente</span>
</button>
<div class="flex flex-col items-end space-y-4">
<div class="flex space-x-2">
<button
type="button"
wire:click="addComponent"
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors flex items-center space-x-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
</svg>
<span>Agregar Componente</span>
</button>
<button
type="button"
wire:click="saveConfiguration"
wire:loading.attr="disabled"
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors flex items-center space-x-2"
>
<svg class="w-4 h-4" wire:loading.remove wire:target="saveConfiguration" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<svg class="w-4 h-4 animate-spin" wire:loading wire:target="saveConfiguration" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span wire:loading.remove wire:target="saveConfiguration">Guardar</span>
<span wire:loading wire:target="saveConfiguration">Guardando...</span>
</button>
</div>
</div>
</div>
<!-- Label con nombres de componentes -->
@@ -22,9 +44,9 @@
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-blue-800">Código:</span>
<div class="flex flex-wrap items-center gap-2">
<span ><flux:badge color="green">SOGOS0001</flux:badge>-</span>
<span ><flux:badge color="green">{{ $project->reference}}</flux:badge>-</span>
@foreach($components as $index => $component)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-white text-blue-700 border border-blue-200">
<span class="inline-flex items-center px-3 py-1 rounded-md text-xs font-medium bg-white text-blue-700 border border-blue-200">
{{ $component['headerLabel'] }}
@if(isset($component['data']['documentTypes']) && count($component['data']['documentTypes']) > 0)
<span class="ml-1 bg-blue-100 text-blue-800 px-1.5 py-0.5 rounded-full">
@@ -36,6 +58,7 @@
<span class="text-blue-400">-</span>
@endif
@endforeach
<span>- <flux:badge color="green">Document Name</flux:badge></span>
</div>
</div>
</div>
@@ -157,6 +180,8 @@
:key="'document-manager-' . $component['id']"
:component-id="$component['id']"
:initial-name="$component['headerLabel']"
:initial-max-length="$component['data']['maxLength'] ?? 3"
:initial-document-types="$component['data']['documentTypes'] ?? []"
/>
</div>
</div>

View File

@@ -101,7 +101,6 @@
<div class="p-6">
<!-- Info Tab -->
<div x-show="activeTab === 'info'">
<div class="flex flex-wrap gap-6">
<!-- Columna Izquierda - Información -->
<div class="w-full md:w-[calc(50%-12px)]">
@@ -141,6 +140,12 @@
Editar
</a>
@can('update', $project)
<a href="{{ route('project-settings.index', $project) }}" class="btn btn-primary">
<x-icons icon="cog-6-tooth" class="w-4 h-4 mr-1" /> Configurar
</a>
@endcan
{{-- Formulario de Edición --}}
<form method="POST" action="{{ route('projects.update', $project) }}">
@csrf
@@ -150,7 +155,7 @@
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
</svg>
{{ $project->is_active ? 'Desactivar' : 'Activar' }}
{{ $project->status == "Activo" ? 'Desactivar' : 'Activar' }}
</button>
</form>

View File

@@ -0,0 +1,377 @@
<x-layouts.app title="{{ 'Configuración del Proyecto - ' . $project->name }}"
:showSidebar={{ $showSidebar }}>
<!-- Header -->
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-2xl font-bold text-gray-900">Configuración del Proyecto</h2>
<p class="text-gray-600">{{ $project->reference }} - {{ $project->name }}</p>
</div>
<flux:button
href="{{ route('projects.show', $project) }}"
icon:trailing="arrow-uturn-left"
variant="ghost"
>
Volver al Proyecto
</flux:button>
</div>
@if(session('success'))
<div class="mb-4 p-4 bg-green-100 text-green-700 rounded-lg">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="mb-4 p-4 bg-red-100 text-red-700 rounded-lg">
{{ session('error') }}
</div>
@endif
<!-- Tabs de navegación -->
<div x-data="{ activeTab: 'coding' }" class="bg-white rounded-lg shadow-md border-1">
<div class="border-b border-gray-200 mb-6">
<nav class="-mb-px flex space-x-8">
<button @click="activeTab = 'coding'"
:class="activeTab === 'coding' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
class="py-4 px-1 border-b-2 font-medium">
Codificación de Documentos
</button>
<button @click="activeTab = 'statuses'"
:class="activeTab === 'statuses' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
class="py-4 px-1 border-b-2 font-medium">
Estados de Documentos
</button>
</nav>
</div>
<div class="p-6">
<!-- Sección de Codificación -->
<div x-show="activeTab === 'coding'">
<div id="coding" class="mb-12">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">Codificación de Documentos</h3>
<div class="text-sm text-gray-500">
Última actualización: {{ $project->codingConfig->updated_at->format('d/m/Y H:i') ?? 'No configurado' }}
</div>
</div>
<livewire:project-name-coder
:project="$project"
/>
</div>
</div>
<!-- Sección de Estados -->
<div x-show="activeTab === 'statuses'">
<div id="statuses">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">Estados de Documentos</h3>
<button type="button"
onclick="openStatusModal()"
class="btn btn-primary">
<x-icons icon="plus" class="w-4 h-4 mr-1" /> Nuevo Estado
</button>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden">
<!-- Lista de estados existentes -->
<div id="statuses-list" class="divide-y divide-gray-200">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal para crear/editar estado -->
<div id="status-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" style="display: none;">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="border-b px-6 py-4">
<h3 class="text-lg font-medium text-gray-900" id="modal-title">Nuevo Estado</h3>
</div>
<form id="status-form" method="POST" class="p-6">
@csrf
<div id="method-field"></div>
<div class="space-y-4">
<!-- Nombre -->
<div>
<label for="modal-name" class="block text-sm font-medium text-gray-700">
Nombre del Estado *
</label>
<input type="text"
id="modal-name"
name="name"
required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
<!-- Color -->
<div class="grid grid-cols-2 gap-4">
<div>
<label for="modal-color" class="block text-sm font-medium text-gray-700">
Color de fondo
</label>
<div class="mt-1 flex items-center">
<input type="color"
id="modal-color"
name="color"
value="#6b7280"
class="h-10 w-16 rounded border">
<input type="text"
id="modal-color-text"
name="color_text"
value="#6b7280"
class="ml-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
</div>
<div>
<label for="modal-text-color" class="block text-sm font-medium text-gray-700">
Color del texto
</label>
<div class="mt-1 flex items-center">
<input type="color"
id="modal-text-color"
name="text_color"
value="#ffffff"
class="h-10 w-16 rounded border">
<input type="text"
id="modal-text-color-text"
name="text_color_text"
value="#ffffff"
class="ml-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
</div>
</div>
</div>
<!-- Descripción -->
<div>
<label for="modal-description" class="block text-sm font-medium text-gray-700">
Descripción
</label>
<textarea id="modal-description"
name="description"
rows="3"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
</div>
<!-- Permisos -->
<div class="grid grid-cols-2 gap-4">
<label class="flex items-center">
<input type="checkbox"
name="allow_upload"
value="1"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<span class="ml-2 text-sm text-gray-700">Permitir subida</span>
</label>
<label class="flex items-center">
<input type="checkbox"
name="allow_edit"
value="1"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<span class="ml-2 text-sm text-gray-700">Permitir edición</span>
</label>
<label class="flex items-center">
<input type="checkbox"
name="allow_delete"
value="1"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-indigo-200 focus:ring-opacity-50">
<span class="ml-2 text-sm text-gray-700">Permitir eliminación</span>
</label>
<label class="flex items-center">
<input type="checkbox"
name="requires_approval"
value="1"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<span class="ml-2 text-sm text-gray-700">Requiere aprobación</span>
</label>
</div>
<!-- Estado por defecto -->
<div>
<label class="flex items-center">
<input type="checkbox"
name="is_default"
value="1"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<span class="ml-2 text-sm text-gray-700">Estado por defecto para nuevos documentos</span>
</label>
<p class="mt-1 text-xs text-gray-500">
Solo puede haber un estado por defecto
</p>
</div>
</div>
<div class="mt-6 flex justify-end space-x-3">
<button type="button" onclick="closeStatusModal()" class="btn btn-secondary">
Cancelar
</button>
<button type="submit" class="btn btn-primary">
Guardar Estado
</button>
</div>
</form>
</div>
</div>
<!-- Modal de confirmación para eliminar -->
<div id="delete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50" style="display: none;">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="border-b px-6 py-4">
<h3 class="text-lg font-medium text-gray-900">Confirmar eliminación</h3>
</div>
<div class="p-6">
<p class="text-gray-700" id="delete-message">¿Estás seguro de que deseas eliminar este estado?</p>
<form id="delete-form" method="POST" class="mt-6">
@csrf
@method('DELETE')
<div class="flex justify-end space-x-3">
<button type="button" onclick="closeDeleteModal()" class="btn btn-secondary">
Cancelar
</button>
<button type="submit" class="btn btn-danger">
Eliminar Estado
</button>
</div>
</form>
</div>
</div>
</div>
@push('scripts')
<script>
// Variables globales
let currentStatusId = null;
const statusModal = document.getElementById('status-modal');
const deleteModal = document.getElementById('delete-modal');
// Funciones para el modal de estados
function openStatusModal(status = null) {
const form = document.getElementById('status-form');
const title = document.getElementById('modal-title');
const methodField = document.getElementById('method-field');
if (status) {
// Modo edición
title.textContent = 'Editar Estado';
form.action = `/projects/{{ $project->id }}/settings/statuses/${status.id}`;
methodField.innerHTML = '<input type="hidden" name="_method" value="PUT">';
// Llenar los campos con los datos del estado
document.getElementById('modal-name').value = status.name;
document.getElementById('modal-color').value = status.color;
document.getElementById('modal-color-text').value = status.color;
document.getElementById('modal-text-color').value = status.text_color;
document.getElementById('modal-text-color-text').value = status.text_color;
document.getElementById('modal-description').value = status.description || '';
// Checkboxes
document.querySelector('input[name="allow_upload"]').checked = status.allow_upload;
document.querySelector('input[name="allow_edit"]').checked = status.allow_edit;
document.querySelector('input[name="allow_delete"]').checked = status.allow_delete;
document.querySelector('input[name="requires_approval"]').checked = status.requires_approval;
document.querySelector('input[name="is_default"]').checked = status.is_default;
currentStatusId = status.id;
} else {
// Modo creación
title.textContent = 'Nuevo Estado';
form.action = `/projects/{{ $project->id }}/settings/statuses`;
methodField.innerHTML = '';
// Resetear campos
form.reset();
document.getElementById('modal-color').value = '#6b7280';
document.getElementById('modal-color-text').value = '#6b7280';
document.getElementById('modal-text-color').value = '#ffffff';
document.getElementById('modal-text-color-text').value = '#ffffff';
currentStatusId = null;
}
statusModal.style.display = 'flex';
}
function closeStatusModal() {
statusModal.style.display = 'none';
}
// Funciones para el modal de eliminación
function openDeleteModal(status) {
const message = document.getElementById('delete-message');
const form = document.getElementById('delete-form');
message.textContent = `¿Estás seguro de que deseas eliminar el estado "${status.name}"?`;
form.action = `/projects/{{ $project->id }}/settings/statuses/${status.id}`;
currentStatusId = status.id;
deleteModal.style.display = 'flex';
}
function closeDeleteModal() {
deleteModal.style.display = 'none';
}
// Sincronizar inputs de color
document.getElementById('modal-color').addEventListener('input', function(e) {
document.getElementById('modal-color-text').value = e.target.value;
});
document.getElementById('modal-color-text').addEventListener('input', function(e) {
document.getElementById('modal-color').value = e.target.value;
});
document.getElementById('modal-text-color').addEventListener('input', function(e) {
document.getElementById('modal-text-color-text').value = e.target.value;
});
document.getElementById('modal-text-color-text').addEventListener('input', function(e) {
document.getElementById('modal-text-color').value = e.target.value;
});
// Cerrar modales al hacer clic fuera
window.addEventListener('click', function(e) {
if (e.target === statusModal) {
closeStatusModal();
}
if (e.target === deleteModal) {
closeDeleteModal();
}
});
// Funcionalidad de drag & drop para reordenar estados
new Sortable(document.getElementById('statuses-list'), {
animation: 150,
handle: '.drag-handle',
onEnd: function() {
// Obtener el nuevo orden
const order = Array.from(document.querySelectorAll('.status-item')).map(item => {
return item.dataset.statusId;
});
// Enviar el nuevo orden al servidor
fetch(`/projects/{{ $project->id }}/settings/statuses/reorder`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ order: order })
});
}
});
</script>
@endpush
</x-layouts.app>

View File

@@ -0,0 +1,68 @@
<div class="status-item p-4 flex items-center hover:bg-gray-50 transition-colors"
data-status-id="{{ $status->id }}">
<!-- Handle para drag & drop -->
<div class="drag-handle cursor-move mr-3 text-gray-400 hover:text-gray-600">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" />
</svg>
</div>
<!-- Badge de color -->
<div class="w-8 h-8 rounded-full mr-3 border"
style="background-color: {{ $status->color }}; color: {{ $status->text_color }}"
title="{{ $status->name }}">
<div class="flex items-center justify-center h-full text-xs font-bold">
{{ substr($status->name, 0, 2) }}
</div>
</div>
<!-- Información del estado -->
<div class="flex-grow">
<div class="flex items-center">
<span class="font-medium text-gray-900">{{ $status->name }}</span>
@if($status->is_default)
<span class="ml-2 px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded-full">
Por defecto
</span>
@endif
</div>
@if($status->description)
<p class="text-sm text-gray-600 mt-1">{{ $status->description }}</p>
@endif
<!-- Permisos -->
<div class="flex items-center space-x-3 mt-2">
@if($status->allow_upload)
<span class="text-xs text-green-600"> Subir</span>
@endif
@if($status->allow_edit)
<span class="text-xs text-blue-600"> Editar</span>
@endif
@if($status->allow_delete)
<span class="text-xs text-red-600"> Eliminar</span>
@endif
@if($status->requires_approval)
<span class="text-xs text-yellow-600"> Aprobación</span>
@endif
</div>
</div>
<!-- Acciones -->
<div class="flex items-center space-x-2">
<button type="button"
onclick="openStatusModal({{ json_encode($status) }})"
class="text-yellow-600 hover:text-yellow-900"
title="Editar">
<x-icons.pencil class="w-5 h-5" />
</button>
@if(!$status->is_default && $status->documents_count == 0)
<button type="button"
onclick="openDeleteModal({{ json_encode($status) }})"
class="text-red-600 hover:text-red-900"
title="Eliminar">
<x-icons.trash class="w-5 h-5" />
</button>
@endif
</div>
</div>

View File

@@ -189,7 +189,6 @@
</tr>
</tbody>
</table>
@livewire('project-name-coder')
</div>
<div class="relative">