updates to document handling and code editing features

This commit is contained in:
2025-12-03 23:27:08 +01:00
parent 88e526cf6c
commit 7b00887372
29 changed files with 20851 additions and 1114 deletions

235
app/Livewire/CodeEdit.php Normal file
View File

@@ -0,0 +1,235 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class CodeEdit extends Component
{
public $componentId; // Nuevo: ID del componente padre
public $name = '';
public $codeInput = '';
public $labelInput = '';
public $maxLength = 3;
public $documentTypes = [];
public $sortBy = 'code';
public $sortDirection = 'asc';
protected $rules = [
'name' => 'required|string|min:2|max:50',
'codeInput' => 'required|string',
'labelInput' => 'required|string',
'maxLength' => 'required|integer|min:2|max:12',
];
public function mount($componentId, $initialName = '')
{
$this->componentId = $componentId;
$this->name = $initialName;
$this->maxLength = 3;
// Disparar evento inicial para establecer el nombre
$this->dispatch('nameUpdated',
componentId: $this->componentId,
data: [
'name' => $this->name
]
);
}
public function updateName()
{
$this->validate([
'name' => 'required|string|min:2|max:50',
]);
$this->dispatch('nameUpdated',
componentId: $this->componentId,
data: [
'name' => $this->name
]
);
}
public function updateMaxLength()
{
$this->validate([
'maxLength' => 'integer|min:2|max:12',
]);
if (strlen($this->codeInput) > $this->maxLength) {
$this->codeInput = substr($this->codeInput, 0, $this->maxLength);
}
}
public function addField()
{
$this->validate([
'codeInput' => "required|string|size:{$this->maxLength}",
'labelInput' => 'required|string|min:1',
], [
'codeInput.size' => "El código debe tener exactamente {$this->maxLength} caracteres",
]);
$this->documentTypes[] = [
'code' => $this->codeInput,
'label' => $this->labelInput,
'max_length' => $this->maxLength,
];
$this->sortList();
$this->reset(['codeInput', 'labelInput']);
$this->dispatch('componentUpdated',
componentId: $this->componentId, // Cambiado aquí
data: [
'documentTypes' => $this->documentTypes,
'maxLength' => $this->maxLength
]
);
}
public function addCode()
{
$this->validate([
'codeInput' => "required|string|size:{$this->maxLength}",
], [
'codeInput.size' => "El código debe tener exactamente {$this->maxLength} caracteres",
]);
$this->dispatch('focus-label-input');
}
public function addLabel()
{
$this->validate([
'labelInput' => 'required|string|min:1',
]);
if (!empty($this->codeInput) && !empty($this->labelInput)) {
$this->addField();
}
}
public function removeField($index)
{
if (isset($this->documentTypes[$index])) {
unset($this->documentTypes[$index]);
$this->documentTypes = array_values($this->documentTypes);
$this->dispatch('componentUpdated',
componentId: $this->componentId, // Cambiado aquí
data: [
'documentTypes' => $this->documentTypes,
'maxLength' => $this->maxLength
]
);
}
}
public function updatedMaxLength($value)
{
$this->validate([
'maxLength' => 'integer|min:2|max:12',
]);
if (strlen($this->codeInput) > $value) {
$this->codeInput = substr($this->codeInput, 0, $value);
}
}
public function updatedCodeInput($value)
{
if (strlen($value) > $this->maxLength) {
$this->codeInput = substr($value, 0, $this->maxLength);
}
$this->codeInput = strtoupper($value);
}
public function sortByCode()
{
if ($this->sortBy === 'code') {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = 'code';
$this->sortDirection = 'asc';
}
$this->sortList();
}
public function sortByLabel()
{
if ($this->sortBy === 'label') {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = 'label';
$this->sortDirection = 'asc';
}
$this->sortList();
}
private function sortList()
{
$direction = $this->sortDirection === 'asc' ? SORT_ASC : SORT_DESC;
if ($this->sortBy === 'code') {
array_multisort(
array_column($this->documentTypes, 'code'),
$direction,
SORT_STRING,
$this->documentTypes
);
} else {
array_multisort(
array_column($this->documentTypes, 'label'),
$direction,
SORT_STRING,
$this->documentTypes
);
}
}
public function getSortedDocumentTypesProperty()
{
$sorted = $this->documentTypes;
$direction = $this->sortDirection === 'asc' ? SORT_ASC : SORT_DESC;
if ($this->sortBy === 'code') {
array_multisort(
array_column($sorted, 'code'),
$direction,
SORT_STRING,
$sorted
);
} else {
array_multisort(
array_column($sorted, 'label'),
$direction,
SORT_STRING,
$sorted
);
}
return $sorted;
}
public function getTotalDocumentTypesProperty()
{
return count($this->documentTypes);
}
public function getSortIcon($column)
{
if ($this->sortBy !== $column) {
return 'sort';
}
return $this->sortDirection === 'asc' ? 'sort-up' : 'sort-down';
}
public function render()
{
return view('livewire.code-edit');
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace App\Livewire;
use App\Models\Document;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\DocumentsExport;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;
use Rappasoft\LaravelLivewireTables\Views\Filters\TextFilter;
class ProjectDocumentList extends DataTableComponent
{
public $projectId;
public $folderId = null;
protected $model = Document::class;
public function configure(): void
{
$this->setPrimaryKey('id')
->setAdditionalSelects(['documents.id as id'])
/*->setConfigurableAreas([
'toolbar-left-start' => ['includes.areas.toolbar-left-start', ['param1' => 'Default', 'param2' => ['param2' => 2]]],
])*/
->setPaginationEnabled()
->setPaginationMethod('simple')
->setPaginationVisibilityEnabled()
//->setReorderEnabled()
->setHideReorderColumnUnlessReorderingEnabled()
->setSecondaryHeaderTrAttributes(function ($rows) {
return ['class' => 'bg-gray-100'];
})
->setSecondaryHeaderTdAttributes(function (Column $column, $rows) {
if ($column->isField('id')) {
return ['class' => 'text-red-100'];
}
return ['default' => true];
})
->setFooterTrAttributes(function ($rows) {
return ['class' => 'bg-gray-100'];
})
->setFooterTdAttributes(function (Column $column, $rows) {
if ($column->isField('name')) {
return ['class' => 'text-green-500'];
}
return ['default' => true];
})
->setHideBulkActionsWhenEmptyEnabled()
->setUseHeaderAsFooterEnabled()
->setPaginationEnabled()
->setPaginationVisibilityEnabled()
//->setToolsDisabled()
//->setToolBarDisabled()
// Configuración de paginación
->setPerPage(25) // Número de elementos por página
->setPerPageAccepted([10, 25, 50, 100]) // Opciones de elementos por página
->setPaginationEnabled() // Asegurar que la paginación esté habilitada
->setPaginationVisibilityStatus(true); // Hacer visible el paginador
;
}
public function mount($projectId = null, $folderId = null)
{
$this->projectId = $projectId;
$this->folderId = $folderId;
}
public function columns(): array
{
return [
Column::make("Código", "code")
->sortable()
->searchable()
->secondaryHeaderFilter('code') // Filtro para esta columna
->format(
fn($value, $row, Column $column) =>
'<a href="'.route('documents.show', $row->id).'" target="_blank" class=" target="_blank" class="flex items-center hover:text-blue-600 transition-colors"">'.$value.'</a>'
)->html(),
Column::make("Nombre", "name")
->sortable()
->searchable()
->secondaryHeaderFilter('name') // Filtro para esta columna
->format(
fn($value, $row, Column $column) =>
'<div class="flex items-center">
<span class="mr-2 text-lg">
'.\App\Helpers\FileHelper::getFileIconSvg($value).'
</span>
<a href="'.route('documents.show', $row->id).'"
target="_blank"
class=" target="_blank" class="flex items-center hover:text-blue-600 transition-colors"">
'.$value.'
</a>
</div>'
)->html(),
Column::make("Estado", "status")
->sortable()
->searchable()
->secondaryHeaderFilter('status') // Filtro para esta columna
->format(
fn($value, $row, Column $column) =>
'<span class="px-2 py-1 text-xs font-semibold rounded-full '.
($value === 'active' ? 'bg-green-100 text-green-800' :
($value === 'pending' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800')).'">'.
$value.'</span>'
)->html(),
Column::make("Revisión", "revision")
->sortable()
->searchable(),
Column::make("Versión", "version")
->sortable()
->searchable(),
Column::make("Área", "area")
->sortable()
->searchable()
->secondaryHeaderFilter('area'), // Filtro para esta columna
Column::make("Disciplina", "discipline")
->sortable()
->searchable()
->secondaryHeaderFilter('discipline'), // Filtro para esta columna
Column::make("Tipo", "document_type")
->sortable()
->searchable()
->secondaryHeaderFilter('type'), // Filtro para esta columna
Column::make("Fecha Entrada", "entry_date")
->sortable()
->searchable()
->format(
fn($value, $row, Column $column) =>
$value ? \Carbon\Carbon::parse($value)->format('d/m/Y') : '-'
),
Column::make("Actualizado", "updated_at")
->sortable()
->searchable()
->format(
fn($value, $row, Column $column) =>
$value ? \Carbon\Carbon::parse($value)->diffForHumans() : '-'
),
];
}
public function filters(): array
{
return [
TextFilter::make('Código', 'code') // Agregar clave 'code'
->config([
'placeholder' => 'Buscar por código',
])
->filter(function (Builder $builder, string $value) {
$builder->where('documents.code', 'like', '%'.$value.'%');
}),
TextFilter::make('Nombre', 'name') // Agregar clave 'name'
->config([
'placeholder' => 'Buscar por nombre',
])
->filter(function (Builder $builder, string $value) {
$builder->where('documents.name', 'like', '%'.$value.'%');
}),
SelectFilter::make('Estado', 'status') // Agregar clave 'status'
->options([
'' => 'Todos',
'active' => 'Activo',
'pending' => 'Pendiente',
'inactive' => 'Inactivo',
])
->filter(function (Builder $builder, string $value) {
if ($value) {
$builder->where('documents.status', $value);
}
}),
SelectFilter::make('Disciplina', 'discipline') // Agregar clave 'discipline'
->options(
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
->prepend('Todas', '')
->toArray()
)
->filter(function (Builder $builder, string $value) {
if ($value) {
$builder->where('documents.discipline', $value);
}
}),
SelectFilter::make('Area', 'area') // Agregar clave 'area'
->options(
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
->prepend('Todas', '')
->toArray()
)
->filter(function (Builder $builder, string $value) {
if ($value) {
$builder->where('documents.area', $value);
}
}),
SelectFilter::make('Tipo', 'type') // Agregar clave 'document_type'
->options(
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
->prepend('Todas', '')
->toArray()
)
->filter(function (Builder $builder, string $value) {
if ($value) {
$builder->where('documents.document_type', $value);
}
}),
];
}
public function builder(): Builder
{
$query = Document::query()->where('project_id', $this->projectId);
if ($this->folderId) {
$query->where('folder_id', $this->folderId);
} else {
$query->whereNull('folder_id');
}
return $query->with('user');
}
public function bulkActions(): array
{
return [
'activate' => 'Activar',
'deactivate' => 'Desactivar',
'export' => 'Exportar',
];
}
public function export()
{
$documents = $this->getSelected();
$this->clearSelected();
return Excel::download(new DocumentsExport($documents), 'documentos.xlsx');
}
public function activate()
{
Document::whereIn('id', $this->getSelected())->update(['status' => 'active']);
$this->clearSelected();
$this->dispatch('documents-updated');
}
public function deactivate()
{
Document::whereIn('id', $this->getSelected())->update(['status' => 'inactive']);
$this->clearSelected();
$this->dispatch('documents-updated');
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class ProjectNameCoder extends Component
{
public $components = [];
public $nextId = 1;
protected $listeners = [
'nameUpdated' => 'headerLabelUpdate',
'componentUpdated' => 'handleComponentUpdate',
'removeComponent' => 'removeComponent'
];
public function mount()
{
// Inicializar con un componente vacío
$this->addComponent();
}
public function addComponent()
{
$id = $this->nextId++;
$this->components[] = [
'id' => $id,
'data' => [],
'order' => count($this->components),
'headerLabel' => ''
];
}
public function removeComponent($componentId)
{
$this->components = array_filter($this->components, function($component) use ($componentId) {
return $component['id'] != $componentId;
});
// Reindexar el orden
$this->reorderComponents();
}
public function headerLabelUpdate($componentId, $data)
{
foreach ($this->components as &$component) {
if ($component['id'] == $componentId) {
$component['headerLabel'] = $data['name'];
break;
}
}
}
public function handleComponentUpdate($componentId, $data)
{
foreach ($this->components as &$component) {
if ($component['id'] == $componentId) {
$component['data'] = $data;
break;
}
}
}
public function updateComponentOrder($orderedIds)
{
foreach ($orderedIds as $index => $id) {
foreach ($this->components as &$component) {
if ($component['id'] == $id) {
$component['order'] = $index;
break;
}
}
}
// Ordenar el array por el campo 'order'
usort($this->components, function($a, $b) {
return $a['order'] - $b['order'];
});
}
private function reorderComponents()
{
foreach ($this->components as $index => &$component) {
$component['order'] = $index;
}
}
public function moveComponentUp($componentId)
{
$index = $this->findComponentIndex($componentId);
if ($index > 0) {
// Intercambiar con el componente anterior
$temp = $this->components[$index];
$this->components[$index] = $this->components[$index - 1];
$this->components[$index - 1] = $temp;
// Actualizar órdenes
$this->reorderComponents();
}
}
public function moveComponentDown($componentId)
{
$index = $this->findComponentIndex($componentId);
if ($index < count($this->components) - 1) {
// Intercambiar con el componente siguiente
$temp = $this->components[$index];
$this->components[$index] = $this->components[$index + 1];
$this->components[$index + 1] = $temp;
// Actualizar órdenes
$this->reorderComponents();
}
}
private function findComponentIndex($componentId)
{
foreach ($this->components as $index => $component) {
if ($component['id'] == $componentId) {
return $index;
}
}
return -1;
}
public function getComponentsCountProperty()
{
return count($this->components);
}
public function getTotalDocumentTypesProperty()
{
$total = 0;
foreach ($this->components as $component) {
if (isset($component['data']['documentTypes'])) {
$total += count($component['data']['documentTypes']);
}
}
return $total;
}
public function render()
{
return view('livewire.project-name-coder');
}
}

View File

@@ -11,6 +11,8 @@ use App\Models\Document;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
use App\Helpers\DocumentIdentifier;
class ProjectShow extends Component
{
@@ -31,6 +33,8 @@ class ProjectShow extends Component
public $selectedFiles = []; // Archivos temporales en el modal
public $uploadProgress = [];
protected $listeners = ['documents-updated' => '$refresh'];
public function mount(Project $project)
{
@@ -80,28 +84,6 @@ class ProjectShow extends Component
$this->project->refresh();
}
/*public function uploadFiles(): void
{
$this->validate([
'files.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png'
]);
dd($this->files);
foreach ($this->files as $file) {
Document::create([
'name' => $file->getClientOriginalName(),
'file_path' => $file->store("projects/{$this->project->id}/documents"),
'project_id' => $this->project->id,
'folder_id' => $this->currentFolder?->id
]);
}
$this->reset('files');
if ($this->currentFolder) {
$this->currentFolder->refresh(); // Recargar documentos
}
$this->reset('files');
}*/
public function getDocumentsProperty()
{
return $this->currentFolder
@@ -158,20 +140,41 @@ class ProjectShow extends Component
$this->selectedFiles = array_values($this->selectedFiles); // Reindexar array
}
// Método para confirmar y guardar
public function uploadFiles(): void
{
foreach ($this->selectedFiles as $file) {
Document::create([
'name' => $file->getClientOriginalName(),
'file_path' => $file->store("projects/{$this->project->id}/documents"),
'project_id' => $this->project->id, // Asegurar que se envía
'folder_id' => $this->currentFolder?->id,
'user_id' => Auth::id(),
//'status' => 'active' // Añadir si tu modelo lo requiere
]);
//$analizador = analizarDocumento();
//print_r($analizador);
//$resultado1 = $analizador->analizarDocumento($file->getClientOriginalName());
$code = $this->project->reference;
// Buscar si ya existe un documento con el mismo nombre en el mismo proyecto y carpeta
$existingDocument = Document::where('project_id', $this->project->id)
->where('folder_id', $this->currentFolder?->id)
->where('name', $file->getClientOriginalName())
->first();
if ($existingDocument) {
// Si existe, crear una nueva versión/revisión
$existingDocument->createVersion($file);
} else {
// Si no existe, crear el documento con revisión 0
$document = Document::create([
'name' => $file->getClientOriginalName(),
'file_path' => $file->store("projects/{$this->project->id}/documents"),
'project_id' => $this->project->id,
'folder_id' => $this->currentFolder?->id,
'issuer_id' => Auth::id(),
'code' => $code,
'entry_date' => now(),
'revision' => 0, // Revisión inicial
]);
$document->createVersion($file);
}
}
$this->resetUpload();
$this->project->refresh();
}
@@ -226,7 +229,8 @@ class ProjectShow extends Component
'file_path' => $path,
'project_id' => $this->project->id,
'folder_id' => $this->currentFolder?->id,
'user_id' => Auth::id()
'user_id' => Auth::id(),
'code' => $code,
]);
} catch (\Exception $e) {
@@ -247,4 +251,9 @@ class ProjectShow extends Component
{
return \App\Helpers\ProjectNamingSchema::generate($fields);
}
public function sellectAllDocuments()
{
$this->selectedFiles = $this->documents->pluck('id')->toArray();
}
}