updates to document handling and code editing features
This commit is contained in:
134
app/Helpers/DocumentIdentifier.php
Normal file
134
app/Helpers/DocumentIdentifier.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
class DocumentIdentifier {
|
||||
// Diccionarios para validar disciplinas y tipos
|
||||
private $disciplinasValidas = [
|
||||
'ENG' => 'Ingeniería',
|
||||
'ARC' => 'Arquitectura',
|
||||
'CIV' => 'Civil',
|
||||
'MEC' => 'Mecánica',
|
||||
'ELC' => 'Eléctrica',
|
||||
'INS' => 'Instrumentación',
|
||||
'PIP' => 'Piping',
|
||||
'STR' => 'Estructural'
|
||||
];
|
||||
|
||||
private $tiposDocumentoValidos = [
|
||||
'DRW' => 'Dibujo',
|
||||
'ESP' => 'Especificación',
|
||||
'LST' => 'Lista de materiales',
|
||||
'PRO' => 'Procedimiento',
|
||||
'INF' => 'Informe',
|
||||
'MAN' => 'Manual',
|
||||
'CAL' => 'Cálculo',
|
||||
'REP' => 'Reporte'
|
||||
];
|
||||
|
||||
public function analizarDocumento($codigoCompleto) {
|
||||
// Validar formato básico
|
||||
if (strpos($codigoCompleto, ' - ') === false) {
|
||||
return $this->crearResultadoError("Formato inválido: falta separador ' - '");
|
||||
}
|
||||
|
||||
list($codigo, $nombre) = explode(' - ', $codigoCompleto, 2);
|
||||
$segmentos = explode('-', $codigo);
|
||||
|
||||
// Validar número de segmentos
|
||||
if (count($segmentos) != 5) {
|
||||
return $this->crearResultadoError("Número incorrecto de segmentos");
|
||||
}
|
||||
|
||||
// Extraer y validar cada parte
|
||||
$codigoProyecto = $segmentos[0];
|
||||
$codigoMaker = $segmentos[1];
|
||||
$disciplina = $segmentos[2];
|
||||
$tipoDocumento = $segmentos[3];
|
||||
$revisionCompleta = $segmentos[4];
|
||||
|
||||
// Validar formato de revisión
|
||||
if (strpos($revisionCompleta, 'REV.') !== 0) {
|
||||
return $this->crearResultadoError("Formato de revisión inválido");
|
||||
}
|
||||
|
||||
$numeroRevision = substr($revisionCompleta, 4); // Remover "REV."
|
||||
|
||||
// Validar número de revisión
|
||||
if (!ctype_digit($numeroRevision) || strlen($numeroRevision) != 2) {
|
||||
return $this->crearResultadoError("Número de revisión inválido");
|
||||
}
|
||||
|
||||
// Validar disciplinas y tipos
|
||||
$disciplinaValida = $this->validarDisciplina($disciplina);
|
||||
$tipoValido = $this->validarTipoDocumento($tipoDocumento);
|
||||
|
||||
return [
|
||||
'codigo_completo' => $codigoCompleto,
|
||||
'codigo_proyecto' => $codigoProyecto,
|
||||
'codigo_maker' => $codigoMaker,
|
||||
'disciplina' => $disciplina,
|
||||
'disciplina_desc' => $disciplinaValida,
|
||||
'tipo_documento' => $tipoDocumento,
|
||||
'tipo_documento_desc' => $tipoValido,
|
||||
'revision' => $numeroRevision,
|
||||
'nombre_documento' => $nombre,
|
||||
'estructura_valida' => true,
|
||||
'errores' => []
|
||||
];
|
||||
}
|
||||
|
||||
private function validarDisciplina($codigo) {
|
||||
return isset($this->disciplinasValidas[$codigo])
|
||||
? $this->disciplinasValidas[$codigo]
|
||||
: "Desconocida";
|
||||
}
|
||||
|
||||
private function validarTipoDocumento($codigo) {
|
||||
return isset($this->tiposDocumentoValidos[$codigo])
|
||||
? $this->tiposDocumentoValidos[$codigo]
|
||||
: "Desconocido";
|
||||
}
|
||||
|
||||
private function crearResultadoError($mensaje) {
|
||||
return [
|
||||
'estructura_valida' => false,
|
||||
'errores' => [$mensaje]
|
||||
];
|
||||
}
|
||||
|
||||
// Método para generar un nuevo código
|
||||
public function generarCodigo($proyecto, $maker, $disciplina, $tipo, $revision, $nombre = '') {
|
||||
$revisionFormateada = str_pad($revision, 2, '0', STR_PAD_LEFT);
|
||||
$codigo = "{$proyecto}-{$maker}-{$disciplina}-{$tipo}-REV.{$revisionFormateada}";
|
||||
|
||||
if (!empty($nombre)) {
|
||||
$codigo .= " - {$nombre}";
|
||||
}
|
||||
|
||||
return $codigo;
|
||||
}
|
||||
}
|
||||
|
||||
// EJEMPLOS DE USO
|
||||
/*
|
||||
$analizador = new DocumentIdentifier();
|
||||
|
||||
// Analizar un código existente
|
||||
$codigo1 = "MP00002-SOGOS-ENG-DRW-REV.01 - Plano principal";
|
||||
$resultado1 = $analizador->analizarDocumento($codigo1);
|
||||
|
||||
echo "Análisis del documento:\n";
|
||||
print_r($resultado1);
|
||||
|
||||
// Generar un nuevo código
|
||||
$nuevoCodigo = $analizador->generarCodigo(
|
||||
'MP00002',
|
||||
'SOGOS',
|
||||
'CIV',
|
||||
'ESP',
|
||||
'03',
|
||||
'Especificación técnica de cimientos'
|
||||
);
|
||||
|
||||
echo "\nNuevo código generado: " . $nuevoCodigo . "\n";*/
|
||||
|
||||
?>
|
||||
@@ -20,4 +20,50 @@ class FileHelper
|
||||
default => 'document'
|
||||
};
|
||||
}
|
||||
|
||||
public static function getFileIconClass($filename)
|
||||
{
|
||||
$fileType = self::getFileType($filename);
|
||||
|
||||
$classes = [
|
||||
'pdf' => 'text-red-500',
|
||||
'word' => 'text-blue-500',
|
||||
'excel' => 'text-green-500',
|
||||
'image' => 'text-yellow-500',
|
||||
'archive' => 'text-purple-500',
|
||||
'text' => 'text-gray-500',
|
||||
'powerpoint' => 'text-orange-500',
|
||||
'document' => 'text-gray-400'
|
||||
];
|
||||
|
||||
return $classes[$fileType] ?? 'text-gray-400';
|
||||
}
|
||||
|
||||
public static function getFileIconSvg($filename)
|
||||
{
|
||||
$fileType = self::getFileType($filename);
|
||||
$colorClass = self::getFileIconClass($filename);
|
||||
|
||||
$icons = [
|
||||
'pdf' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
</svg>',
|
||||
'word' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
</svg>',
|
||||
'excel' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
</svg>',
|
||||
'image' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/>
|
||||
</svg>',
|
||||
'archive' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/>
|
||||
</svg>'
|
||||
];
|
||||
|
||||
return $icons[$fileType] ?? '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
||||
</svg>';
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,8 @@ class DocumentController extends Controller
|
||||
|
||||
$document->url = Storage::url($document->file_path);
|
||||
|
||||
$document->load('user');
|
||||
|
||||
return view('documents.show', [
|
||||
'document' => $document,
|
||||
'versions' => $document->versions()->latest()->get(),
|
||||
@@ -108,7 +110,7 @@ class DocumentController extends Controller
|
||||
foreach ($request->file('files') as $file) {
|
||||
$document = $project->documents()->create([
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'status' => 'pending'
|
||||
'status' => 0
|
||||
]);
|
||||
|
||||
$this->createVersion($document, $file);
|
||||
@@ -125,4 +127,331 @@ class DocumentController extends Controller
|
||||
|
||||
$document->update(['current_version_id' => $version->id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Actualizar PDF con anotaciones, firmas y sellos
|
||||
*/
|
||||
public function updatePdf(Request $request, Document $document)
|
||||
{
|
||||
$this->authorize('update', $document);
|
||||
|
||||
$request->validate([
|
||||
'pdf_data' => 'required|string', // PDF modificado en base64
|
||||
'annotations' => 'sometimes|array',
|
||||
'signatures' => 'sometimes|array',
|
||||
'stamps' => 'sometimes|array',
|
||||
]);
|
||||
|
||||
try {
|
||||
// Procesar el PDF modificado
|
||||
$modifiedPdf = $this->processPdfData($request->pdf_data);
|
||||
|
||||
// Reemplazar el archivo original (sin crear nueva versión si no quieres)
|
||||
$this->replaceOriginalPdf($document, $modifiedPdf);
|
||||
|
||||
// Opcional: también crear una nueva versión para historial
|
||||
$newVersion = $this->createNewVersion($document, $modifiedPdf, $request->all());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'PDF actualizado correctamente',
|
||||
'version_id' => $newVersion->id ?? null,
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error updating PDF: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al actualizar el PDF: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar datos del PDF en base64
|
||||
*/
|
||||
private function processPdfData($pdfData)
|
||||
{
|
||||
// Eliminar el prefijo data:application/pdf;base64, si existe
|
||||
$pdfData = preg_replace('/^data:application\/pdf;base64,/', '', $pdfData);
|
||||
|
||||
// Decodificar base64
|
||||
$pdfContent = base64_decode($pdfData);
|
||||
|
||||
if (!$pdfContent) {
|
||||
throw new \Exception('Datos PDF inválidos');
|
||||
}
|
||||
|
||||
return $pdfContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesar PDF con anotaciones (método alternativo)
|
||||
*/
|
||||
private function processPdfWithAnnotations($document, $data)
|
||||
{
|
||||
// Aquí integrarías una librería PHP para PDF como spatie/pdf-to-image o setasign/fpdi
|
||||
// Por ahora, devolvemos el contenido del archivo original
|
||||
// En producción, implementarías la lógica de modificación
|
||||
|
||||
if ($document->currentVersion) {
|
||||
$filePath = $document->currentVersion->file_path;
|
||||
} else {
|
||||
$filePath = $document->getFirstMedia('documents')->getPath();
|
||||
}
|
||||
|
||||
if (!Storage::exists($filePath)) {
|
||||
throw new \Exception('Archivo PDF no encontrado');
|
||||
}
|
||||
|
||||
return Storage::get($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reemplazar el PDF original
|
||||
*/
|
||||
private function replaceOriginalPdf($document, $pdfContent)
|
||||
{
|
||||
// Obtener la ruta del archivo original
|
||||
$filePath = $document->file_path;
|
||||
|
||||
// Si el documento usa media library
|
||||
if ($document->getFirstMedia('documents')) {
|
||||
$media = $document->getFirstMedia('documents');
|
||||
$media->update([
|
||||
'file_name' => $document->name . '.pdf',
|
||||
'size' => strlen($pdfContent),
|
||||
]);
|
||||
|
||||
// Reemplazar el archivo
|
||||
Storage::put($media->getPath(), $pdfContent);
|
||||
} else {
|
||||
// Si usas file_path directo
|
||||
Storage::put($filePath, $pdfContent);
|
||||
|
||||
// Actualizar metadata del documento
|
||||
$document->update([
|
||||
'file_size' => strlen($pdfContent),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear nueva versión del documento
|
||||
*/
|
||||
private function createNewVersion($document, $pdfContent, $data = [])
|
||||
{
|
||||
$versionNumber = $document->versions()->count() + 1;
|
||||
$fileName = "documents/{$document->id}/v{$versionNumber}.pdf";
|
||||
|
||||
// Guardar el nuevo PDF
|
||||
Storage::put($fileName, $pdfContent);
|
||||
|
||||
// Crear registro de versión
|
||||
$version = $document->versions()->create([
|
||||
'version_number' => $versionNumber,
|
||||
'file_path' => $fileName,
|
||||
'file_size' => strlen($pdfContent),
|
||||
'hash' => hash('sha256', $pdfContent),
|
||||
'created_by' => auth()->id(),
|
||||
'metadata' => [
|
||||
'annotations_count' => count($data['annotations'] ?? []),
|
||||
'signatures_count' => count($data['signatures'] ?? []),
|
||||
'stamps_count' => count($data['stamps'] ?? []),
|
||||
'edited_at' => now()->toISOString(),
|
||||
'edited_by' => auth()->user()->name
|
||||
]
|
||||
]);
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guardar metadatos de anotaciones
|
||||
*/
|
||||
private function saveAnnotationsMetadata($version, $data)
|
||||
{
|
||||
// Guardar anotaciones en la base de datos si es necesario
|
||||
if (!empty($data['annotations'])) {
|
||||
foreach ($data['annotations'] as $annotation) {
|
||||
$version->annotations()->create([
|
||||
'type' => $annotation['type'] ?? 'text',
|
||||
'content' => $annotation['content'] ?? '',
|
||||
'position' => $annotation['position'] ?? [],
|
||||
'page' => $annotation['page'] ?? 1,
|
||||
'created_by' => auth()->id()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subir firma
|
||||
*/
|
||||
public function uploadSignature(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'signature' => 'required|image|max:2048|mimes:png,jpg,jpeg'
|
||||
]);
|
||||
|
||||
try {
|
||||
$user = auth()->user();
|
||||
$path = $request->file('signature')->store("signatures/{$user->id}", 'public');
|
||||
|
||||
// Opcional: Guardar en base de datos
|
||||
$user->signatures()->create([
|
||||
'file_path' => $path,
|
||||
'file_name' => $request->file('signature')->getClientOriginalName()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'path' => Storage::url($path),
|
||||
'filename' => basename($path)
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error uploading signature: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al subir la firma'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subir sello
|
||||
*/
|
||||
public function uploadStamp(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'stamp' => 'required|image|max:2048|mimes:png,jpg,jpeg'
|
||||
]);
|
||||
|
||||
try {
|
||||
$user = auth()->user();
|
||||
$path = $request->file('stamp')->store("stamps/{$user->id}", 'public');
|
||||
|
||||
// Opcional: Guardar en base de datos
|
||||
$user->stamps()->create([
|
||||
'file_path' => $path,
|
||||
'file_name' => $request->file('stamp')->getClientOriginalName(),
|
||||
'type' => $request->type ?? 'custom'
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'path' => Storage::url($path),
|
||||
'filename' => basename($path)
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error uploading stamp: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al subir el sello'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener firmas del usuario
|
||||
*/
|
||||
public function getSignatures()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$signatures = $user->signatures()->get()->map(function($signature) {
|
||||
return [
|
||||
'id' => $signature->id,
|
||||
'url' => Storage::url($signature->file_path),
|
||||
'name' => $signature->file_name
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'signatures' => $signatures
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener sellos del usuario
|
||||
*/
|
||||
public function getStamps()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$stamps = $user->stamps()->get()->map(function($stamp) {
|
||||
return [
|
||||
'id' => $stamp->id,
|
||||
'url' => Storage::url($stamp->file_path),
|
||||
'name' => $stamp->file_name,
|
||||
'type' => $stamp->type
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'stamps' => $stamps
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Descargar documento
|
||||
*/
|
||||
public function download(Document $document, $versionId = null)
|
||||
{
|
||||
$this->authorize('view', $document);
|
||||
|
||||
$version = $versionId ?
|
||||
$document->versions()->findOrFail($versionId) :
|
||||
$document->currentVersion;
|
||||
|
||||
if (!$version || !Storage::exists($version->file_path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return Storage::download($version->file_path, $document->name . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener el PDF actual para edición
|
||||
*/
|
||||
public function getPdfForEditing(Document $document)
|
||||
{
|
||||
$this->authorize('view', $document);
|
||||
|
||||
try {
|
||||
if ($document->getFirstMedia('documents')) {
|
||||
$filePath = $document->getFirstMedia('documents')->getPath();
|
||||
} else {
|
||||
$filePath = $document->file_path;
|
||||
}
|
||||
|
||||
if (!Storage::exists($filePath)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$pdfContent = Storage::get($filePath);
|
||||
$base64Pdf = base64_encode($pdfContent);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'pdf_data' => $base64Pdf,
|
||||
'document_name' => $document->name
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Error getting PDF for editing: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error al cargar el PDF'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Folder;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
@@ -11,7 +12,7 @@ use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
use AuthorizesRequests; // ← Añadir este trait
|
||||
use AuthorizesRequests;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
@@ -43,7 +44,7 @@ class ProjectController extends Controller
|
||||
'project' => $project,
|
||||
'categories' => Category::orderBy('name')->get(),
|
||||
'users' => User::where('id', '!=', auth()->id())->get(),
|
||||
'companies' => \App\Models\Company::all(), // Pass companies to the view
|
||||
'companies' => \App\Models\Company::all(), // Pass companies to the view,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -93,6 +94,12 @@ class ProjectController extends Controller
|
||||
if($request->has('categories')) {
|
||||
$project->categories()->sync($request->categories);
|
||||
}
|
||||
|
||||
Folder::create([
|
||||
'name' => 'Project',
|
||||
'project_id' => $project->id,
|
||||
'parent_id' => null,
|
||||
]);
|
||||
|
||||
return redirect()->route('projects.show', $project)->with('success', 'Proyecto creado exitosamente');
|
||||
|
||||
@@ -172,6 +179,9 @@ class ProjectController extends Controller
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function __invoke(Project $project)
|
||||
{
|
||||
return view('projects.show', [
|
||||
|
||||
235
app/Livewire/CodeEdit.php
Normal file
235
app/Livewire/CodeEdit.php
Normal 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');
|
||||
}
|
||||
}
|
||||
273
app/Livewire/ProjectDocumentList.php
Normal file
273
app/Livewire/ProjectDocumentList.php
Normal 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');
|
||||
}
|
||||
}
|
||||
149
app/Livewire/ProjectNameCoder.php
Normal file
149
app/Livewire/ProjectNameCoder.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Events\DocumentVersionUpdated;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Document extends Model
|
||||
{
|
||||
@@ -20,22 +22,63 @@ class Document extends Model
|
||||
'file_path',
|
||||
'project_id', // Asegurar que está en fillable
|
||||
'folder_id',
|
||||
'user_id',
|
||||
'issuer',
|
||||
'status',
|
||||
'revision',
|
||||
'version',
|
||||
'discipline',
|
||||
'document_type',
|
||||
'issuer',
|
||||
'entry_date',
|
||||
'current_version_id',
|
||||
'code',
|
||||
];
|
||||
|
||||
|
||||
public function versions() {
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($document) {
|
||||
if (request()->hasFile('file')) {
|
||||
$file = request()->file('file');
|
||||
$document->createVersion($file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all versions of the document.
|
||||
*/
|
||||
public function versions(): HasMany
|
||||
{
|
||||
return $this->hasMany(DocumentVersion::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current version of the document.
|
||||
*/
|
||||
public function currentVersion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(DocumentVersion::class, 'current_version_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest version of the document.
|
||||
*/
|
||||
public function getLatestVersionAttribute()
|
||||
{
|
||||
return $this->versions()->latestFirst()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new version from file content.
|
||||
*/
|
||||
public function createVersion(string $content, array $changes = [], ?User $user = null): DocumentVersion
|
||||
{
|
||||
$version = DocumentVersion::createFromContent($this, $content, $changes, $user);
|
||||
|
||||
// Update current version pointer
|
||||
$this->update(['current_version_id' => $version->id]);
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
public function approvals() {
|
||||
return $this->hasMany(Approval::class);
|
||||
@@ -44,17 +87,6 @@ class Document extends Model
|
||||
public function comments() {
|
||||
return $this->hasMany(Comment::class)->whereNull('parent_id');
|
||||
}
|
||||
|
||||
|
||||
public function createVersion($file)
|
||||
{
|
||||
return $this->versions()->create([
|
||||
'file_path' => $file->store("documents/{$this->id}/versions"),
|
||||
'hash' => hash_file('sha256', $file),
|
||||
'user_id' => auth()->id(),
|
||||
'version_number' => $this->versions()->count() + 1
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCurrentVersionAttribute()
|
||||
{
|
||||
@@ -63,7 +95,7 @@ class Document extends Model
|
||||
|
||||
public function uploadVersion($file)
|
||||
{
|
||||
$this->versions()->create([
|
||||
$version = $this->versions()->create([
|
||||
'file_path' => $file->store("projects/{$this->id}/versions"),
|
||||
'hash' => hash_file('sha256', $file),
|
||||
'version' => $this->versions()->count() + 1,
|
||||
@@ -82,4 +114,9 @@ class Document extends Model
|
||||
->logUnguarded();
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,320 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DocumentVersion extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'document_id',
|
||||
'file_path',
|
||||
'hash',
|
||||
'version',
|
||||
'user_id',
|
||||
'changes',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'changes' => 'array',
|
||||
'version' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be appended to the model's array form.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $appends = [
|
||||
'file_url',
|
||||
'file_size_formatted',
|
||||
'created_at_formatted',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boot function for model events
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($model) {
|
||||
// Asegurar que la versión sea incremental para el mismo documento
|
||||
if (empty($model->version)) {
|
||||
$lastVersion = self::where('document_id', $model->document_id)
|
||||
->max('version');
|
||||
$model->version = $lastVersion ? $lastVersion + 1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
static::deleting(function ($model) {
|
||||
// No eliminar el archivo físico si hay otras versiones que lo usan
|
||||
$sameFileCount = self::where('file_path', $model->file_path)
|
||||
->where('id', '!=', $model->id)
|
||||
->count();
|
||||
|
||||
if ($sameFileCount === 0 && Storage::exists($model->file_path)) {
|
||||
Storage::delete($model->file_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document that owns the version.
|
||||
*/
|
||||
public function document(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Document::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who created the version.
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file URL for the version.
|
||||
*/
|
||||
public function getFileUrlAttribute(): string
|
||||
{
|
||||
return Storage::url($this->file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file size in a human-readable format.
|
||||
*/
|
||||
public function getFileSizeFormattedAttribute(): string
|
||||
{
|
||||
if (!Storage::exists($this->file_path)) {
|
||||
return '0 B';
|
||||
}
|
||||
|
||||
$bytes = Storage::size($this->file_path);
|
||||
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$index = 0;
|
||||
|
||||
while ($bytes >= 1024 && $index < count($units) - 1) {
|
||||
$bytes /= 1024;
|
||||
$index++;
|
||||
}
|
||||
|
||||
return round($bytes, 2) . ' ' . $units[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual file size in bytes.
|
||||
*/
|
||||
public function getFileSizeAttribute(): int
|
||||
{
|
||||
return Storage::exists($this->file_path) ? Storage::size($this->file_path) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted created_at date.
|
||||
*/
|
||||
public function getCreatedAtFormattedAttribute(): string
|
||||
{
|
||||
return $this->created_at->format('d/m/Y H:i');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version label (v1, v2, etc.)
|
||||
*/
|
||||
public function getVersionLabelAttribute(): string
|
||||
{
|
||||
return 'v' . $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the current version of the document.
|
||||
*/
|
||||
public function getIsCurrentAttribute(): bool
|
||||
{
|
||||
return $this->document->current_version_id === $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get changes summary for display.
|
||||
*/
|
||||
public function getChangesSummaryAttribute(): string
|
||||
{
|
||||
if (empty($this->changes)) {
|
||||
return 'Sin cambios registrados';
|
||||
}
|
||||
|
||||
$changes = $this->changes;
|
||||
|
||||
if (is_array($changes)) {
|
||||
$summary = [];
|
||||
|
||||
if (isset($changes['annotations_count']) && $changes['annotations_count'] > 0) {
|
||||
$summary[] = $changes['annotations_count'] . ' anotación(es)';
|
||||
}
|
||||
|
||||
if (isset($changes['signatures_count']) && $changes['signatures_count'] > 0) {
|
||||
$summary[] = $changes['signatures_count'] . ' firma(s)';
|
||||
}
|
||||
|
||||
if (isset($changes['stamps_count']) && $changes['stamps_count'] > 0) {
|
||||
$summary[] = $changes['stamps_count'] . ' sello(s)';
|
||||
}
|
||||
|
||||
if (isset($changes['edited_by'])) {
|
||||
$summary[] = 'por ' . $changes['edited_by'];
|
||||
}
|
||||
|
||||
return implode(', ', $summary);
|
||||
}
|
||||
|
||||
return (string) $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include versions of a specific document.
|
||||
*/
|
||||
public function scopeOfDocument($query, $documentId)
|
||||
{
|
||||
return $query->where('document_id', $documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to order versions by latest first.
|
||||
*/
|
||||
public function scopeLatestFirst($query)
|
||||
{
|
||||
return $query->orderBy('version', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to order versions by oldest first.
|
||||
*/
|
||||
public function scopeOldestFirst($query)
|
||||
{
|
||||
return $query->orderBy('version', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous version.
|
||||
*/
|
||||
public function getPreviousVersion(): ?self
|
||||
{
|
||||
return self::where('document_id', $this->document_id)
|
||||
->where('version', '<', $this->version)
|
||||
->orderBy('version', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next version.
|
||||
*/
|
||||
public function getNextVersion(): ?self
|
||||
{
|
||||
return self::where('document_id', $this->document_id)
|
||||
->where('version', '>', $this->version)
|
||||
->orderBy('version', 'asc')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exists in storage.
|
||||
*/
|
||||
public function fileExists(): bool
|
||||
{
|
||||
return Storage::exists($this->file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file content.
|
||||
*/
|
||||
public function getFileContent(): ?string
|
||||
{
|
||||
return $this->fileExists() ? Storage::get($this->file_path) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify file integrity using hash.
|
||||
*/
|
||||
public function verifyIntegrity(): bool
|
||||
{
|
||||
if (!$this->fileExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentHash = hash_file('sha256', Storage::path($this->file_path));
|
||||
return $currentHash === $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new version from file content.
|
||||
*/
|
||||
public static function createFromContent(Document $document, string $content, array $changes = [], ?User $user = null): self
|
||||
{
|
||||
$user = $user ?: auth()->user();
|
||||
|
||||
// Calcular hash
|
||||
$hash = hash('sha256', $content);
|
||||
|
||||
// Determinar siguiente versión
|
||||
$lastVersion = self::where('document_id', $document->id)->max('version');
|
||||
$version = $lastVersion ? $lastVersion + 1 : 1;
|
||||
|
||||
// Guardar archivo
|
||||
$filePath = "documents/{$document->id}/versions/v{$version}.pdf";
|
||||
Storage::put($filePath, $content);
|
||||
|
||||
// Crear registro
|
||||
return self::create([
|
||||
'document_id' => $document->id,
|
||||
'file_path' => $filePath,
|
||||
'hash' => $hash,
|
||||
'version' => $version,
|
||||
'user_id' => $user->id,
|
||||
'changes' => $changes,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore this version as the current version.
|
||||
*/
|
||||
public function restoreAsCurrent(): bool
|
||||
{
|
||||
if (!$this->fileExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear una nueva versión idéntica a esta
|
||||
$content = $this->getFileContent();
|
||||
$newVersion = self::createFromContent(
|
||||
$this->document,
|
||||
$content,
|
||||
['restored_from' => 'v' . $this->version]
|
||||
);
|
||||
|
||||
// Actualizar documento para apuntar a la nueva versión
|
||||
$this->document->update([
|
||||
'current_version_id' => $newVersion->id
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user