diff --git a/app/Helpers/FileHelper.php b/app/Helpers/FileHelper.php new file mode 100644 index 0000000..df40db8 --- /dev/null +++ b/app/Helpers/FileHelper.php @@ -0,0 +1,23 @@ + 'pdf', + 'doc', 'docx' => 'word', + 'xls', 'xlsx' => 'excel', + 'ppt', 'pptx' => 'powerpoint', + 'jpg', 'jpeg', 'png', 'gif' => 'image', + 'zip', 'rar' => 'archive', + 'txt' => 'text', + default => 'document' + }; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/DocumentCommentController.php b/app/Http/Controllers/DocumentCommentController.php new file mode 100644 index 0000000..2e6cc8c --- /dev/null +++ b/app/Http/Controllers/DocumentCommentController.php @@ -0,0 +1,82 @@ +validate([ + 'content' => 'required|string|max:1000', + 'page' => 'required|integer|min:1', + 'x' => 'required|numeric|between:0,1', + 'y' => 'required|numeric|between:0,1' + ]); + + $document->comments()->create([ + 'user_id' => auth()->id(), + 'content' => $request->content, + 'page' => $request->page, + 'x' => $request->x, + 'y' => $request->y, + 'parent_id' => $request->parent_id + ]); + + return back(); + } + + /** + * Display the specified resource. + */ + public function show(DocumentComment $documentComment) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(DocumentComment $documentComment) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, DocumentComment $documentComment) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(DocumentComment $documentComment) + { + // + } +} diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 64066fd..07b2dc8 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -6,9 +6,13 @@ use App\Jobs\ProcessDocumentOCR; use App\Models\Document; use App\Models\Project; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; class DocumentController extends Controller { + public $comments=[]; + + /** * Display a listing of the resource. */ @@ -56,7 +60,19 @@ class DocumentController extends Controller */ public function show(Document $document) { - // + $this->authorize('view', $document); // Si usas políticas + + if (!Storage::exists($document->file_path)) { + abort(404); + } + + $document->url = Storage::url($document->file_path); + + return view('documents.show', [ + 'document' => $document, + 'versions' => $document->versions()->latest()->get(), + 'comments' => $this->comments, + ]); } /** diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index bdb91dc..19109cc 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -17,12 +17,16 @@ class ProjectController extends Controller */ public function index() { - $projects = Project::withCount('documents') - ->whereHas('users', function($query) { + $projects = auth()->user()->hasRole('admin') + ? Project::get() // Todos los proyectos para admin + : auth()->user()->projects()->latest()->get(); // Solo proyectos asignados + + /* + $projects = Project::whereHas('users', function($query) { $query->where('user_id', auth()->id()); }) ->filter(['search' => request('search')]) - ->paginate(9); + ->paginate(9);*/ return view('projects.index', compact('projects')); } @@ -47,24 +51,24 @@ class ProjectController extends Controller { $validated = $request->validate([ 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'status' => 'required|in:active,inactive', - 'team' => 'sometimes|array', - 'team.*' => 'exists:users,id', + 'description' => 'nullable|string', + 'status' => 'required|in:Activo,Inactivo', + //'team' => 'sometimes|array', + //'team.*' => 'exists:users,id', 'project_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', 'address' => 'nullable|string|max:255', 'province' => 'nullable|string|max:100', - 'country' => 'nullable|string|size:2', + //'country' => 'nullable|string|size:2', 'postal_code' => 'nullable|string|max:10', 'latitude' => 'required|numeric|between:-90,90', 'longitude' => 'required|numeric|between:-180,180', - 'icon' => 'nullable|in:'.implode(',', config('project.icons')), + //'icon' => 'nullable|in:'.implode(',', config('project.icons')), 'start_date' => 'nullable|date', 'deadline' => 'nullable|date|after:start_date', - 'categories' => 'array|exists:categories,id', + 'categories' => 'nullable|array|exists:categories,id', //'categories' => 'required|array', - 'categories.*' => 'exists:categories,id', - 'documents.*' => 'file|max:5120|mimes:pdf,docx,xlsx,jpg,png' + //'categories.*' => 'exists:categories,id', + //'documents.*' => 'file|max:5120|mimes:pdf,docx,xlsx,jpg,png' ]); @@ -89,6 +93,7 @@ class ProjectController extends Controller } // Manejar documentos adjuntos + /* if($request->hasFile('documents')) { foreach ($request->file('documents') as $file) { $project->documents()->create([ @@ -96,14 +101,12 @@ class ProjectController extends Controller 'original_name' => $file->getClientOriginalName() ]); } - } + }*/ - return redirect()->route('projects.show', $project) - ->with('success', 'Proyecto creado exitosamente'); + return redirect()->route('projects.show', $project)->with('success', 'Proyecto creado exitosamente'); } catch (\Exception $e) { - return back()->withInput() - ->with('error', 'Error al crear el proyecto: ' . $e->getMessage()); + return back()->withInput()->with('error', 'Error al crear el proyecto: ' . $e->getMessage()); } } @@ -112,7 +115,7 @@ class ProjectController extends Controller */ public function show(Project $project) { - $this->authorize('view', $project); // Si usas políticas + //$this->authorize('view', $project); // Si usas políticas $project->load(['categories', 'documents']); return view('projects.show', [ diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index d1f8824..b29234d 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -5,7 +5,14 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\User; use Illuminate\Support\Facades\Hash; -use App\Http\Requests\UpdateUserRequest; +use App\Rules\PasswordRule; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Rules\Password; +use Illuminate\Validation\ValidationException; +use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; class UserController extends Controller @@ -13,7 +20,7 @@ class UserController extends Controller public function index() { $this->authorize('viewAny', User::class); - $users = User::with('roles')->paginate(10); + $users = User::paginate(10); return view('users.index', compact('users')); } @@ -27,24 +34,62 @@ class UserController extends Controller public function store(Request $request) { $this->authorize('create', User::class); - - $data = $request->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users', - 'password' => 'required|min:8|confirmed', - 'roles' => 'array' - ]); - - $user = User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']) - ]); - - $user->syncRoles($data['roles'] ?? []); - - return redirect()->route('users.index') - ->with('success', 'Usuario creado exitosamente'); + try { + // Validación de datos + $validated = $request->validate([ + 'title' => 'nullable|string|max:10', + 'first_name' => 'required|string|max:50', + 'last_name' => 'required|string|max:50', + 'username' => 'required|string|unique:users|max:30', + 'password' => ['required', + new PasswordRule( + minLength: 12, + requireUppercase: true, + requireNumeric: true, + requireSpecialCharacter: true, + //uncompromised: true, // Verificar contra Have I Been Pwned + //requireLetters: true + ), + Password::defaults()->mixedCase()->numbers()->symbols() + ->uncompromised(3) // Número mínimo de apariciones en brechas + ], + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + 'email' => 'required|email|unique:users', + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255', + 'profile_photo_path' => 'nullable|string' // Ruta de la imagen subida por Livewire + ]); + + // Creación del usuario + $user = User::create([ + 'title' => $validated['title'], + 'first_name' => $validated['first_name'], + 'last_name' => $validated['last_name'], + 'username' => $validated['username'], + 'password' => Hash::make($validated['password']), + 'email' => $validated['email'], + 'phone' => $validated['phone'], + 'address' => $validated['address'], + 'access_start' => $validated['start_date'], + 'access_end' => $validated['end_date'], + 'is_active' => true, + 'profile_photo_path' => $validated['profile_photo_path'] ?? null + ]); + + if ($request->hasFile('image_path')) { + $path = $request->file('image_path')->store('public/photos'); + $user->profile_photo_path = basename($path); + $user->save(); + } + + // Asignación de roles (opcional, usando Spatie Permissions) + // $user->assignRole('user'); + + return redirect()->route('users.index')->with('success', 'Usuario creado exitosamente.')->with('temp_password', $validated['password']);; + } catch (\Exception $e) { + return back()->withInput()->with('error', 'Error al crear el usuario: ' . $e->getMessage()); + } } public function edit(User $user) @@ -52,17 +97,110 @@ class UserController extends Controller $this->authorize('update', $user); $roles = Role::all(); $userRoles = $user->roles->pluck('id')->toArray(); - - return view('users.edit', compact('user', 'roles', 'userRoles')); + return view('users.create', compact('user', 'roles', 'userRoles')); } - public function update(UpdateUserRequest $request, User $user) + public function update(Request $request, User $user) { - $user->update($request->validated()); - $user->syncRoles($request->roles); - - return redirect()->route('users.index') - ->with('success', 'Usuario actualizado correctamente'); + try { + // Validación de datos + $validated = $request->validate([ + 'title' => 'nullable|string|max:10', + 'first_name' => 'required|string|max:50', + 'last_name' => 'required|string|max:50', + 'username' => [ + 'required', + 'string', + 'max:30', + Rule::unique('users')->ignore($user->id) + ], + 'password' => [ + 'nullable', + Password::min(12) + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised(3) + ], + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + 'email' => [ + 'required', + 'email', + Rule::unique('users')->ignore($user->id) + ], + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255', + 'profile_photo_path' => 'nullable|string', // Añadido para la ruta de la imagen + //'is_active' => 'nullable|boolean' // Añadido para el estado activo + ]); + + // Preparar datos para actualización + $updateData = [ + 'title' => $validated['title'], + 'first_name' => $validated['first_name'], + 'last_name' => $validated['last_name'], + 'username' => $validated['username'], + 'email' => $validated['email'], + 'phone' => $validated['phone'], + 'address' => $validated['address'], + 'access_start' => $validated['start_date'], + 'access_end' => $validated['end_date'], + 'is_active' => $validated['is_active'] ?? false, + 'profile_photo_path' => $validated['profile_photo_path'] ?? $user->profile_photo_path + ]; + + // Actualizar contraseña solo si se proporciona + if (!empty($validated['password'])) { + $updateData['password'] = Hash::make($validated['password']); + } + + // Eliminar imagen anterior si se está actualizando + if (isset($validated['profile_photo_path']) && $user->profile_photo_path) { + Storage::disk('public')->delete($user->profile_photo_path); + } + + // Actualizar el usuario + $user->update($updateData); + + // Redireccionar con mensaje de éxito + return redirect()->route('users.show', $user) + ->with('success', 'Usuario actualizado exitosamente'); + + } catch (ValidationException $e) { + return redirect()->back()->withErrors($e->validator)->withInput(); + + } catch (QueryException $e) { + $errorCode = $e->errorInfo[1]; + $errorMessage = 'Error al actualizar el usuario: '; + + if ($errorCode == 1062) { + $errorMessage .= 'El nombre de usuario o correo electrónico ya está en uso'; + } else { + $errorMessage .= 'Error en la base de datos'; + } + + Log::error("Error actualizando usuario ID {$user->id}: " . $e->getMessage()); + return redirect()->back()->with('error', $errorMessage)->withInput(); + + } catch (\Exception $e) { + Log::error("Error general actualizando usuario ID {$user->id}: " . $e->getMessage()); + return redirect()->back()->with('error', 'Ocurrió un error inesperado al actualizar el usuario')->withInput(); + } + } + + public function show(User $user) + { + $previousUser = User::where('id', '<', $user->id)->latest('id')->first(); + $nextUser = User::where('id', '>', $user->id)->oldest('id')->first(); + $permissionGroups = $this->getPermissionGroups($user); + + return view('users.show', [ + 'user' => $user, + 'previousUser' => $previousUser, + 'nextUser' => $nextUser, + 'permissionGroups' => $permissionGroups, + ]); } public function updatePassword(Request $request, User $user) @@ -94,4 +232,45 @@ class UserController extends Controller return redirect()->route('users.index') ->with('success', 'Usuario eliminado correctamente'); } + + private function getPermissionGroups(User $user) + { + // Obtener todos los permisos disponibles + $allPermissions = Permission::all(); + + // Agrupar permisos por tipo (asumiendo que los nombres siguen el formato "tipo.acción") + $grouped = $allPermissions->groupBy(function ($permission) { + return explode('.', $permission->name)[0]; // Extrae "user" de "user.create" + }); + + // Formatear para la vista + $groups = []; + foreach ($grouped as $groupName => $permissions) { + $groups[$groupName] = [ + 'name' => ucfirst($groupName), + 'permissions' => $permissions->map(function ($permission) use ($user) { + return [ + 'id' => $permission->id, + 'name' => $permission->name, + 'description' => $this->getPermissionDescription($permission->name), + 'enabled' => $user->hasPermissionTo($permission) + ]; + }) + ]; + } + + return $groups; + } + + private function getPermissionDescription($permissionName) + { + $descriptions = [ + 'user.create' => 'Crear nuevos usuarios', + 'user.edit' => 'Editar usuarios existentes', + 'document.view' => 'Ver documentos', + // Agrega más descripciones según necesites + ]; + + return $descriptions[$permissionName] ?? str_replace('.', ' ', $permissionName); + } } \ No newline at end of file diff --git a/app/Livewire/CountrySelect.php b/app/Livewire/CountrySelect.php new file mode 100644 index 0000000..3e8098c --- /dev/null +++ b/app/Livewire/CountrySelect.php @@ -0,0 +1,66 @@ + 'required', + ]; + + public function mount($initialCountry = null) + { + $this->selectedCountry = $initialCountry; + } + + public function selectCountry($code) + { + $this->selectedCountry = $code; + $this->isOpen = false; + $this->search = ''; // Limpiar la búsqueda al seleccionar + } + + public function render() + { + $countries = collect(config('countries')) + ->when($this->search, function($collection) { + // Corregimos el filtrado aquí + return $collection->filter(function($name, $code) { + $searchLower = strtolower($this->search); + $nameLower = strtolower($name); + $codeLower = strtolower($code); + + return str_contains($nameLower, $searchLower) || + str_contains($codeLower, $searchLower); + }); + }); + + return view('livewire.country-select', compact('countries')); + } + + public function formattedCountry($code, $name) + { + return $this->getFlagEmoji($code).' '.$name.' ('.strtoupper($code).')'; + } + + protected function getFlagEmoji($countryCode) + { + $countryCode = strtoupper($countryCode); + $flagOffset = 0x1F1E6; + $asciiOffset = 0x41; + + $firstChar = ord($countryCode[0]) - $asciiOffset + $flagOffset; + $secondChar = ord($countryCode[1]) - $asciiOffset + $flagOffset; + + return mb_convert_encoding('&#'.intval($firstChar).';', 'UTF-8', 'HTML-ENTITIES') + . mb_convert_encoding('&#'.intval($secondChar).';', 'UTF-8', 'HTML-ENTITIES'); + } +} diff --git a/app/Livewire/ImageUploader.php b/app/Livewire/ImageUploader.php new file mode 100644 index 0000000..fbc18cf --- /dev/null +++ b/app/Livewire/ImageUploader.php @@ -0,0 +1,86 @@ +fieldName = $fieldName; + $this->label = $label; + } + + public function updatedImage() + { + $this->validate([ + 'image' => 'image|max:2048', // 2MB Max + ]); + + // Subir automáticamente al seleccionar + $this->uploadImage(); + } + + public function uploadImage() + { + $this->validate([ + 'image' => 'required|image|max:2048', + ]); + + // Guardar la imagen + $this->imagePath = $this->image->store('uploads', 'public'); + // Emitir evento con el nombre del campo y la ruta + $this->dispatch('imageUploaded', + field: $this->fieldName, + path: $this->imagePath + ); + } + + public function save() + { + $this->validate([ + 'image' => 'required|image|max:2048', + ]); + + // Guardar la imagen + $this->imagePath = $this->image->store('images', 'public'); + + // Emitir evento con el nombre del campo y la ruta + $this->emit('imageUploaded', [ + 'field' => $this->fieldName, + 'path' => $this->imagePath + ]); + } + + public function removeImage() + { + $this->reset(['image', 'imagePath']); + + // Cambiar emit por dispatch en Livewire v3+ + $this->dispatch('imageRemoved', + field: $this->fieldName + ); + } + + public function toggleHover($status) + { + $this->hover = $status; + } + + public function render() + { + return view('livewire.image-uploader'); + } +} diff --git a/app/Livewire/ProjectShow.php b/app/Livewire/ProjectShow.php index 90f8a66..c76080c 100644 --- a/app/Livewire/ProjectShow.php +++ b/app/Livewire/ProjectShow.php @@ -9,12 +9,15 @@ use App\Models\Project; use App\Models\Folder; use App\Models\Document; use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\Auth; class ProjectShow extends Component { use WithFileUploads; + protected $middleware = ['auth']; // Añade esto + public Project $project; public ?Folder $currentFolder = null; public $expandedFolders = []; @@ -22,8 +25,13 @@ class ProjectShow extends Component public $folderName = ''; public $selectedFolderId = null; + public $showFolderModal = false; + public $showUploadModal = false; + public $tempFiles = []; + public $selectedFiles = []; // Archivos temporales en el modal + public $uploadProgress = []; - + public function mount(Project $project) { $this->project = $project->load('rootFolders'); @@ -57,27 +65,27 @@ class ProjectShow extends Component }) ] ]); - Folder::create([ 'name' => $this->folderName, 'project_id' => $this->project->id, - 'parent_id' => $this->currentFolder?->id + 'parent_id' => $this->currentFolder?->id, ]); + $this->hideCreateFolderModal(); $this->reset('folderName'); - $this->project->load('rootFolders'); // Recargar carpetas raíz + $this->project->load('rootFolders'); // Recargar carpetas raíz if ($this->currentFolder) { $this->currentFolder->load('children'); // Recargar hijos si está en una subcarpeta } $this->project->refresh(); } - public function uploadFiles(): void + /*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(), @@ -92,7 +100,7 @@ class ProjectShow extends Component $this->currentFolder->refresh(); // Recargar documentos } $this->reset('files'); - } + }*/ public function getDocumentsProperty() { @@ -116,11 +124,128 @@ class ProjectShow extends Component return $breadcrumbs; } + public function showCreateFolderModal() + { + $this->folderName = ''; + $this->showFolderModal = true; + } + + public function hideCreateFolderModal() + { + $this->showFolderModal = false; + } + + // Método para abrir el modal + public function openUploadModal(): void + { + $this->showUploadModal = true; + } + + // Método para manejar archivos seleccionados + public function selectFiles($files): void + { + $this->validate([ + 'selectedFiles.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png' + ]); + + $this->selectedFiles = array_merge($this->selectedFiles, $files); + } + + // Método para eliminar un archivo de la lista + public function removeFile($index): void + { + unset($this->selectedFiles[$index]); + $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 + ]); + } + + $this->resetUpload(); + $this->project->refresh(); + } + + // Método para procesar los archivos + protected function processFiles(): void + { + foreach ($this->files 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 + ]); + } + } + + // Método para resetear + public function resetUpload(): void + { + $this->reset(['selectedFiles', 'showUploadModal', 'uploadProgress']); + } + + #[On('upload-progress')] + public function updateProgress($name, $progress) + { + $this->uploadProgress[$name] = $progress; + } + + public function addFiles($files) + { + $this->validate([ + 'selectedFiles.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png' + ]); + + $this->selectedFiles = array_merge($this->selectedFiles, $files); + } + + public function startUpload() + { + foreach ($this->selectedFiles as $file) { + try { + $path = $file->store( + "projects/{$this->project->id}/".($this->currentFolder ? "folders/{$this->currentFolder->id}" : ""), + 'public' + ); + + Document::create([ + 'name' => $file->getClientOriginalName(), + 'file_path' => $path, + 'project_id' => $this->project->id, + 'folder_id' => $this->currentFolder?->id, + 'user_id' => Auth::id() + ]); + + } catch (\Exception $e) { + $this->addError('upload', "Error subiendo {$file->getClientOriginalName()}: {$e->getMessage()}"); + } + } + + $this->resetUpload(); + $this->project->refresh(); + } + public function render() { - return view('livewire.project-show') - ->layout('layouts.livewire-app', [ - 'title' => $this->project->name - ]); + return view('livewire.project.show')->layout('components.layouts.documents', [ + 'title' => __('Project: :name', ['name' => $this->project->name]), + 'breadcrumbs' => [ + ['name' => __('Projects'), 'url' => route('projects.index')], + ['name' => $this->project->name, 'url' => route('projects.show', $this->project)], + ], + ]); } } diff --git a/app/Livewire/UserPhotoUpload.php b/app/Livewire/UserPhotoUpload.php new file mode 100644 index 0000000..0877948 --- /dev/null +++ b/app/Livewire/UserPhotoUpload.php @@ -0,0 +1,81 @@ + 'refreshPhoto']; + + public function mount($existingPhoto = null, $userId = null) + { + $this->existingPhoto = $existingPhoto; + $this->userId = $userId; + } + + public function uploadPhoto() + { + $this->validate([ + 'photo' => 'image|max:2048|mimes:jpg,png,jpeg,gif', + ]); + + $this->tempPhoto = $this->photo->temporaryUrl(); + } + + public function updatedPhoto() + { + $this->validate([ + 'photo' => 'image|max:2048|mimes:jpg,png,jpeg,gif', + ]); + + $this->tempPhoto = $this->photo->temporaryUrl(); + + // Emitir evento con la foto temporal + $this->emit('photoUploaded', $this->photo->getRealPath()); + } + + public function removePhoto() + { + $this->reset('photo', 'tempPhoto'); + } + + public function deletePhoto() + { + if ($this->existingPhoto) { + Storage::delete('public/photos/' . $this->existingPhoto); + + if ($this->userId) { + $user = User::find($this->userId); + $user->update(['profile_photo_path' => null]); + } + + $this->existingPhoto = null; + $this->emit('photoDeleted'); + } + } + + public function refreshPhoto() + { + $this->existingPhoto = User::find($this->userId)->profile_photo_path; + } + + public function render() + { + return view('livewire.user-photo-upload', [ + //'photo' => $this->photo, + 'tempPhoto' => $this->tempPhoto, + 'existingPhoto' => $this->existingPhoto, + ]); + } +} diff --git a/app/Livewire/UserTable.php b/app/Livewire/UserTable.php new file mode 100644 index 0000000..33a6f45 --- /dev/null +++ b/app/Livewire/UserTable.php @@ -0,0 +1,109 @@ + true, + 'is_active' => true, + 'username' => true, + 'email' => true, + 'phone' => false, + 'access_start' => false, + 'created_at' => false, + 'is_active' => false, + ]; + + public $filters = [ + 'full_name' => '', + 'is_active' => '', + 'username' => '', + 'email' => '', + 'phone' => '', + 'access_start' => '', + 'created_at' => '' + ]; + + protected $queryString = [ + 'filters' => ['except' => ''], + 'columns' => ['except' => ''] + ]; + + public function mount() + { + // Recuperar preferencias de columnas de la sesión + $this->columns = session('user_columns', $this->columns); + } + + public function updatedColumns() + { + // Guardar preferencias en sesión + session(['user_columns' => $this->columns]); + } + + public function sortBy($field) + { + if ($this->sortField === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortDirection = 'asc'; + } + + $this->sortField = $field; + } + + public function render() + { + $users = User::query() + ->when($this->filters['full_name'], fn($q, $search) => + $q->whereRaw("CONCAT(title, ' ', first_name, ' ', last_name) LIKE ?", ["%$search%"])) + ->when($this->sortField === 'full_name', fn($q) => + $q->orderByRaw("CONCAT(title, ' ', first_name, ' ', last_name) " . $this->sortDirection) + ) + ->when($this->sortField === 'created_at', fn($q) => + $q->orderBy('created_at', $this->sortDirection) + ) + ->when($this->sortField === 'access_start', fn($q) => + $q->orderBy('access_start', $this->sortDirection) + ) + ->when(in_array($this->sortField, ['username', 'email', 'phone']), fn($q) => + $q->orderBy($this->sortField, $this->sortDirection) + ) + ->when($this->filters['full_name'], fn($q, $search) => + $q->whereRaw("CONCAT(title, ' ', first_name, ' ', last_name) LIKE ?", ["%$search%"])) + ->when($this->filters['username'], fn($q, $search) => + $q->where('username', 'LIKE', "%$search%")) + ->when($this->filters['email'], fn($q, $search) => + $q->where('email', 'LIKE', "%$search%")) + ->when($this->filters['phone'], fn($q, $search) => + $q->where('phone', 'LIKE', "%$search%")) + ->when($this->filters['access_start'], fn($q, $date) => + $q->whereDate('access_start', $date)) + ->when($this->filters['created_at'], fn($q, $date) => + $q->whereDate('created_at', $date)) + ->paginate(10); + + return view('livewire.user-table', [ + 'users' => $users, + 'available_columns' => [ + 'full_name' => 'Nombre Completo', + 'username' => 'Usuario', + 'email' => 'Correo', + 'phone' => 'Teléfono', + 'access_start' => 'Fecha de acceso', + 'created_at' => 'Fecha creación', + 'is_active' => 'Estado' + ] + ]); + } +} \ No newline at end of file diff --git a/app/Models/Comment.php b/app/Models/Comment.php new file mode 100644 index 0000000..31d3d13 --- /dev/null +++ b/app/Models/Comment.php @@ -0,0 +1,36 @@ +belongsTo(User::class); + } + + public function document() + { + return $this->belongsTo(Document::class); + } + + public function replies() + { + return $this->hasMany(Comment::class, 'parent_id'); + } +} diff --git a/app/Models/Document.php b/app/Models/Document.php index 31fc596..a160ea9 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -17,10 +17,11 @@ class Document extends Model protected $fillable = [ 'name', - 'status', - 'project_id', + 'file_path', + 'project_id', // Asegurar que está en fillable 'folder_id', - 'current_version_id' + 'user_id', + 'status' ]; @@ -36,6 +37,7 @@ class Document extends Model return $this->hasMany(Comment::class)->whereNull('parent_id'); } + public function createVersion($file) { return $this->versions()->create([ diff --git a/app/Models/DocumentComment.php b/app/Models/DocumentComment.php new file mode 100644 index 0000000..760bca8 --- /dev/null +++ b/app/Models/DocumentComment.php @@ -0,0 +1,22 @@ +belongsTo(User::class); + } + + public function children() { + return $this->hasMany(DocumentComment::class, 'parent_id'); + } + + public function parent() { + return $this->belongsTo(DocumentComment::class, 'parent_id'); + } +} diff --git a/app/Models/Folder.php b/app/Models/Folder.php index f5d7845..dfe1912 100644 --- a/app/Models/Folder.php +++ b/app/Models/Folder.php @@ -10,9 +10,9 @@ class Folder extends Model 'name', 'parent_id', 'project_id', - 'icon', - 'color', - 'description', + //'icon', + //'color', + //'description', ]; public function descendants() diff --git a/app/Models/User.php b/app/Models/User.php index 4d18b93..38d0396 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Spatie\Permission\Traits\HasRoles; @@ -21,9 +22,18 @@ class User extends Authenticatable * @var list */ protected $fillable = [ - 'name', + 'title', + 'first_name', + 'last_name', + 'username', 'email', 'password', + 'phone', + 'address', + 'access_start', + 'access_end', + 'is_active', + 'profile_photo_path', ]; /** @@ -39,26 +49,25 @@ class User extends Authenticatable /** * Get the attributes that should be cast. * - * @return array + * @var list */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - 'is_protected' => 'boolean', - ]; - } + protected $casts = [ + 'email_verified_at' => 'datetime', + 'access_start' => 'date', + 'access_end' => 'date', + 'is_active' => 'boolean' + ]; /** * Get the user's initials */ public function initials(): string { - return Str::of($this->name) + /*return Str::of($this->name) ->explode(' ') ->map(fn (string $name) => Str::of($name)->substr(0, 1)) - ->implode(''); + ->implode('');*/ + return Str::of(''); } public function groups() @@ -89,5 +98,16 @@ class User extends Authenticatable return $group->hasAnyPermission($permissions); }); } + + public function getFullNameAttribute() + { + return "{$this->title} {$this->first_name} {$this->last_name}"; + } + + // Accesor para la URL completa + public function getProfilePhotoUrlAttribute() + { + return $this->profile_photo ? Storage::url($this->profile_photo) : asset('images/default-user.png'); + } } diff --git a/app/Policies/DashboardPolicy.php b/app/Policies/DashboardPolicy.php index aa42cea..89b7490 100644 --- a/app/Policies/DashboardPolicy.php +++ b/app/Policies/DashboardPolicy.php @@ -16,6 +16,6 @@ class DashboardPolicy public function view(User $user) { - return $user->hasPermissionTo('view dashboard'); + return true; //$user->hasPermissionTo('view.dashboard'); } } diff --git a/app/Policies/DocumentPolicy.php b/app/Policies/DocumentPolicy.php index 2c04a8b..a1d2cec 100644 --- a/app/Policies/DocumentPolicy.php +++ b/app/Policies/DocumentPolicy.php @@ -16,7 +16,7 @@ class DocumentPolicy */ public function viewAny(User $user): bool { - return false; + return $user->hasPermissionTo('document.view'); } /** @@ -24,7 +24,7 @@ class DocumentPolicy */ public function view(User $user, Document $document) { - return $user->hasPermissionTo('view documents') + return $user->hasPermissionTo('document.view') && $user->hasProjectAccess($document->project_id) && $user->hasPermissionToResource($document->resource(), 'view'); } @@ -42,7 +42,7 @@ class DocumentPolicy */ public function update(User $user, Document $document): bool { - return $user->hasPermissionToResource($document->resource(), 'edit'); + return $user->hasPermissionToResource($document->resource(), 'document.edit'); } /** @@ -50,7 +50,7 @@ class DocumentPolicy */ public function delete(User $user, Document $document): bool { - return $user->hasPermissionTo('delete documents'); + return $user->hasPermissionTo('document.delete'); } /** diff --git a/app/Policies/FolderPolicy.php b/app/Policies/FolderPolicy.php index bec6d85..46040f0 100644 --- a/app/Policies/FolderPolicy.php +++ b/app/Policies/FolderPolicy.php @@ -22,18 +22,18 @@ class FolderPolicy $user->projects->contains($folder->project_id); } - return $user->can('manage-projects'); + return $user->can('project.create'); } public function move(User $user, Folder $folder) { - return $user->can('manage-projects') && + return $user->can('project.create') && $user->projects->contains($folder->project_id); } public function delete(User $user, Folder $folder) { - return $user->can('delete-projects') && + return $user->can('project.delete') && $user->projects->contains($folder->project_id); } } diff --git a/app/Policies/PermissionPolicy.php b/app/Policies/PermissionPolicy.php index 6a844b1..bcedf92 100644 --- a/app/Policies/PermissionPolicy.php +++ b/app/Policies/PermissionPolicy.php @@ -14,7 +14,7 @@ class PermissionPolicy */ public function viewAny(User $user): bool { - return $user->hasPermissionTo('view permissions'); + return $user->hasPermissionTo('permission.view'); } /** @@ -22,7 +22,7 @@ class PermissionPolicy */ public function view(User $user, Permission $permission): bool { - return $user->hasPermissionTo('view permissions'); + return $user->hasPermissionTo('permission.view'); } /** @@ -30,7 +30,7 @@ class PermissionPolicy */ public function create(User $user): bool { - return $user->hasPermissionTo('create permissions'); + return $user->hasPermissionTo('permission.create'); } /** @@ -40,7 +40,7 @@ class PermissionPolicy { if($permission->is_system) return false; - return $user->hasPermissionTo('edit permissions'); + return $user->hasPermissionTo('permission.edit'); } /** @@ -52,7 +52,7 @@ class PermissionPolicy return false; } - return $user->hasPermissionTo('delete permissions'); + return $user->hasPermissionTo('permission.delete'); } /** @@ -60,7 +60,7 @@ class PermissionPolicy */ public function restore(User $user, Permission $permission): bool { - return $user->hasPermissionTo('manage permissions'); + return $user->hasPermissionTo('permission.create'); } /** @@ -68,6 +68,6 @@ class PermissionPolicy */ public function forceDelete(User $user, Permission $permission): bool { - return $user->hasPermissionTo('manage permissions'); + return $user->hasPermissionTo('permission.delete'); } } diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php index 43d32d4..be16525 100644 --- a/app/Policies/ProjectPolicy.php +++ b/app/Policies/ProjectPolicy.php @@ -13,7 +13,7 @@ class ProjectPolicy */ public function viewAny(User $user): bool { - return $user->hasPermissionTo('view projects'); + return $user->hasPermissionTo('project.view'); } /** @@ -21,7 +21,13 @@ class ProjectPolicy */ public function view(User $user, Project $project): bool { - return $user->hasPermissionTo('view projects') && + // Admin ve todo, otros usuarios solo proyectos asignados + /* + return $user->hasRole('admin') || + $project->users->contains($user->id) || + $project->manager_id === $user->id;*/ + + return $user->hasPermissionTo('project.view') && $this->hasProjectAccess($user, $project); } @@ -30,7 +36,7 @@ class ProjectPolicy */ public function create(User $user): bool { - return $user->hasPermissionTo('create projects'); + return $user->hasPermissionTo('project.create'); } /** @@ -38,7 +44,7 @@ class ProjectPolicy */ public function update(User $user, Project $project): bool { - return $user->hasPermissionTo('edit projects') && + return $user->hasPermissionTo('project.edit') && $this->hasProjectAccess($user, $project); } @@ -47,7 +53,7 @@ class ProjectPolicy */ public function delete(User $user, Project $project): bool { - return $user->hasPermissionTo('delete projects') && + return $user->hasPermissionTo('project.delete') && $this->hasProjectAccess($user, $project); } diff --git a/app/Policies/RolePolicy.php b/app/Policies/RolePolicy.php index 2857b99..b8a56b0 100644 --- a/app/Policies/RolePolicy.php +++ b/app/Policies/RolePolicy.php @@ -13,7 +13,7 @@ class RolePolicy */ public function viewAny(User $user): bool { - return $user->hasPermissionTo('view roles'); + return $user->hasPermissionTo('role.view'); } /** @@ -21,7 +21,7 @@ class RolePolicy */ public function view(User $user, Role $role): bool { - return false; + return $user->hasPermissionTo('role.view'); } /** @@ -29,7 +29,7 @@ class RolePolicy */ public function create(User $user): bool { - return $user->hasPermissionTo('create roles'); + return $user->hasPermissionTo('role.create'); } /** @@ -37,7 +37,7 @@ class RolePolicy */ public function update(User $user, Role $role): bool { - return $user->hasPermissionTo('edit roles') && !$role->is_protected; + return $user->hasPermissionTo('role.edit') && !$role->is_protected; } /** @@ -45,7 +45,7 @@ class RolePolicy */ public function delete(User $user, Role $role): bool { - return $user->hasPermissionTo('delete roles') && !$role->is_protected; + return $user->hasPermissionTo('role.delete') && !$role->is_protected; } /** diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php index 991a983..9cac0db 100644 --- a/app/Policies/UserPolicy.php +++ b/app/Policies/UserPolicy.php @@ -12,7 +12,7 @@ class UserPolicy */ public function viewAny(User $user): bool { - return $user->hasPermissionTo('manage users'); + return $user->hasPermissionTo('user.view'); } /** @@ -20,7 +20,7 @@ class UserPolicy */ public function view(User $user, User $model): bool { - return false; + return $user->hasPermissionTo('user.view'); } /** @@ -28,7 +28,7 @@ class UserPolicy */ public function create(User $user): bool { - return $user->hasPermissionTo('manage users'); + return $user->hasPermissionTo('user.create'); } /** @@ -36,7 +36,7 @@ class UserPolicy */ public function update(User $user, User $model): bool { - return $user->hasPermissionTo('manage users') && !$model->is_protected; + return $user->hasPermissionTo('user.create') && !$model->is_protected; } /** @@ -44,7 +44,7 @@ class UserPolicy */ public function delete(User $user, User $model): bool { - return $user->hasPermissionTo('manage users') + return $user->hasPermissionTo('user.delete') && !$model->is_protected && $user->id !== $model->id; } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 16a7dc1..c7dfb71 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,11 @@ namespace App\Providers; +use App\Livewire\FileUpload; +use App\Livewire\ImageUploader; +use App\Livewire\ProjectShow; +use App\Livewire\Toolbar; +use App\View\Components\Multiselect; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Validator; @@ -25,12 +30,13 @@ class AppServiceProvider extends ServiceProvider { // Configuración de componentes Blade Blade::componentNamespace('App\\View\\Components', 'icons'); - Blade::component('multiselect', \App\View\Components\Multiselect::class); + Blade::component('multiselect', Multiselect::class); // Registro de componentes Livewire - Livewire::component('project-show', \App\Http\Livewire\ProjectShow::class); - Livewire::component('file-upload', \App\Http\Livewire\FileUpload::class); - Livewire::component('toolbar', \App\Http\Livewire\Toolbar::class); + Livewire::component('project-show', ProjectShow::class); + Livewire::component('file-upload', FileUpload::class); + Livewire::component('toolbar', Toolbar::class); + Livewire::component('image-uploader', ImageUploader::class); // Validación personalizada Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) { diff --git a/app/Providers/AppServiceProvider.php.back b/app/Providers/AppServiceProvider.php.back deleted file mode 100644 index 002dbb1..0000000 --- a/app/Providers/AppServiceProvider.php.back +++ /dev/null @@ -1,66 +0,0 @@ - UserPolicy::class, - User::class => ProfilePolicy::class, - Role::class => RolePolicy::class, - Permission::class => PermissionPolicy::class, - Document::class => DocumentPolicy::class, - Project::class => ProjectPolicy::class, - Folder::class => FolderPolicy::class, - ]; - - /** - * Register any application services. - */ - public function register(): void - { - // - } - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - // - Blade::componentNamespace('App\\View\\Components', 'icons'); - Blade::component('multiselect', \App\View\Components\Multiselect::class); - - Livewire::component('project-show', \App\Http\Livewire\ProjectShow::class); - Livewire::component('project-show', \App\Http\Livewire\FileUpload::class); - Livewire::component('toolbar', \App\Http\Livewire\Toolbar::class); - - Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) { - $maxSize = env('MAX_UPLOAD_SIZE', 51200); // Default 50MB - $totalSize = array_reduce($value, function($sum, $file) { - return $sum + $file->getSize(); - }, 0); - - return $totalSize <= ($maxSize * 1024); - }); - } -} diff --git a/app/Rules/PasswordRule.php b/app/Rules/PasswordRule.php new file mode 100644 index 0000000..cf2b8fe --- /dev/null +++ b/app/Rules/PasswordRule.php @@ -0,0 +1,69 @@ +minLength = $minLength; + $this->requireUppercase = $requireUppercase; + $this->requireNumeric = $requireNumeric; + $this->requireSpecialCharacter = $requireSpecialCharacter; + } + + /** + * Run the validation rule. + * + * @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + // + } + + public function passes($attribute, $value) + { + $passes = true; + + if (strlen($value) < $this->minLength) { + $passes = false; + } + + if ($this->requireUppercase && !preg_match('/[A-Z]/', $value)) { + $passes = false; + } + + if ($this->requireNumeric && !preg_match('/[0-9]/', $value)) { + $passes = false; + } + + if ($this->requireSpecialCharacter && !preg_match('/[\W_]/', $value)) { + $passes = false; + } + + return $passes; + } + + public function message() + { + return 'La contraseña debe contener al menos '.$this->minLength.' caracteres'. + ($this->requireUppercase ? ', una mayúscula' : ''). + ($this->requireNumeric ? ', un número' : ''). + ($this->requireSpecialCharacter ? ' y un carácter especial' : ''); + } +} diff --git a/config/countries.php b/config/countries.php index 0e6fa1d..678c667 100644 --- a/config/countries.php +++ b/config/countries.php @@ -1,8 +1,253 @@ 'España', - 'FR' => 'Francia', + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libyan Arab Jamahiriya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestinian Territory, Occupied', 'PT' => 'Portugal', - // ... resto de países + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', ]; \ No newline at end of file diff --git a/database/migrations/2025_04_19_000001_create_folders_table.php b/database/migrations/2025_04_19_000001_create_folders_table.php index 9fd4895..02d6f8d 100644 --- a/database/migrations/2025_04_19_000001_create_folders_table.php +++ b/database/migrations/2025_04_19_000001_create_folders_table.php @@ -15,7 +15,7 @@ return new class extends Migration $table->id(); $table->string('name'); $table->foreignId('project_id')->constrained(); - $table->foreignId('creator_id')->constrained('users'); + //$table->foreignId('creator_id')->constrained('users'); $table->timestamps(); }); } diff --git a/database/migrations/2025_04_29_115501_update_users_table.php b/database/migrations/2025_04_29_115501_update_users_table.php new file mode 100644 index 0000000..909e911 --- /dev/null +++ b/database/migrations/2025_04_29_115501_update_users_table.php @@ -0,0 +1,51 @@ +string('title')->nullable()->after('id'); + $table->string('first_name')->after('title'); + $table->string('last_name')->after('first_name'); + $table->string('username')->unique()->after('last_name'); + $table->date('access_start')->nullable()->after('password'); + $table->date('access_end')->nullable()->after('access_start'); + $table->string('phone')->nullable()->after('email'); + $table->text('address')->nullable()->after('phone'); + $table->boolean('is_active')->default(true)->after('address'); + $table->string('profile_photo_path')->nullable(); + + // Eliminar campos no necesarios + $table->dropColumn('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('title'); + $table->dropColumn('first_name'); + $table->dropColumn('last_name'); + $table->dropColumn('username'); + $table->dropColumn('access_start'); + $table->dropColumn('access_end'); + $table->dropColumn('phone'); + $table->dropColumn('address'); + $table->dropColumn('is_active'); + $table->dropColumn('profile_photo_path '); + + $table->string('name')->after('id'); + }); + } +}; diff --git a/database/migrations/2025_05_03_202403_add_path_documents_table.php b/database/migrations/2025_05_03_202403_add_path_documents_table.php new file mode 100644 index 0000000..9fdce98 --- /dev/null +++ b/database/migrations/2025_05_03_202403_add_path_documents_table.php @@ -0,0 +1,28 @@ +string('file_path')->require(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('documents', function (Blueprint $table) { + $table->dropColumn('file_path'); + }); + } +}; diff --git a/database/migrations/2025_05_03_230019_create_document_comments_table.php b/database/migrations/2025_05_03_230019_create_document_comments_table.php new file mode 100644 index 0000000..ac3a9c3 --- /dev/null +++ b/database/migrations/2025_05_03_230019_create_document_comments_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('document_id')->constrained(); + $table->foreignId('user_id')->constrained(); + $table->foreignId('parent_id')->nullable()->constrained('document_comments'); + $table->text('content'); + $table->unsignedInteger('page'); + $table->decimal('x', 5, 3); // Posición X normalizada (0-1) + $table->decimal('y', 5, 3); // Posición Y normalizada (0-1) + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('document_comments'); + } +}; diff --git a/database/migrations/2025_05_06_151318_create_comments_table.php b/database/migrations/2025_05_06_151318_create_comments_table.php new file mode 100644 index 0000000..a59c684 --- /dev/null +++ b/database/migrations/2025_05_06_151318_create_comments_table.php @@ -0,0 +1,34 @@ +id(); + $table->text('content'); + $table->foreignId('user_id')->constrained(); + $table->foreignId('document_id')->constrained(); + $table->foreignId('parent_id')->nullable()->constrained('comments'); + $table->unsignedInteger('page'); + $table->decimal('x', 5, 3); // Posición X (0-1) + $table->decimal('y', 5, 3); // Posición Y (0-1) + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('comments'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 30d4447..0abc822 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,9 +3,8 @@ namespace Database\Seeders; use App\Models\User; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; -use Spatie\Permission\Models\Permission; +use Illuminate\Support\Facades\Hash; class DatabaseSeeder extends Seeder { @@ -16,10 +15,14 @@ class DatabaseSeeder extends Seeder { // User::factory(10)->create(); - User::factory()->create([ - 'name' => 'admin', + User::create([ + 'first_name' => 'Administrador', + 'last_name' => '', + 'username' => 'admin', 'email' => 'admin@example.com', - 'password' => '12345678', + 'password' => Hash::make('12345678'), + 'is_active' => true, + 'access_start' => now(), ]); $this->call([ diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index f99fa4d..73d02b4 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -13,6 +13,7 @@ class PermissionSeeder extends Seeder */ public function run() { + /* $permissions = [ // Permissions for Projects 'create projects', @@ -49,6 +50,22 @@ class PermissionSeeder extends Seeder 'name' => $permission, 'guard_name' => 'web' ]); + }*/ + + $permissions = [ + 'user' => ['view', 'create', 'edit', 'delete'], + 'document' => ['view', 'upload', 'edit', 'delete', 'approve'], + 'project' => ['view', 'create', 'edit', 'delete'], + 'comment' => ['create', 'edit', 'delete'], + 'folder' => ['view', 'create', 'edit', 'delete'], + 'role' => ['view', 'create', 'edit', 'delete'], + 'permission' => ['view', 'create', 'edit', 'delete'], + ]; + + foreach ($permissions as $type => $actions) { + foreach ($actions as $action) { + Permission::create(['name' => "{$type}.{$action}"]); + } } } } diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 21505bd..602afc8 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -23,48 +23,10 @@ class RolePermissionSeeder extends Seeder //['description' => 'Administrador del sistema'] ); - // Obtener o crear todos los permisos existentes - $permissions = Permission::all(); - - if ($permissions->isEmpty()) { - // Crear permisos básicos si no existen - $permissions = collect([ - 'view projects', - 'edit projects', - 'delete projects', - 'view roles', - 'create roles', - 'edit roles', - 'delete roles', - 'view permissions', - 'create permissions', - 'edit permissions', - 'delete permissions', - 'assign permissions', - 'revoke permissions', - - ])->map(function ($permission) { - return Permission::updateOrCreate( - ['name' => $permission], - ['guard_name' => 'web'] - ); - }); - } - // Sincronizar todos los permisos con el rol admin $allPermissions = Permission::all(); $adminRole->syncPermissions($allPermissions); - $adminRole->syncPermissions($permissions); - // Crear usuario admin si no existe - /*User::updateOrCreate( - ['email' => env('ADMIN_EMAIL', 'admin@example.com')], - [ - 'name' => 'Administrador', - 'password' => bcrypt(env('ADMIN_PASSWORD', 'password')), - 'email_verified_at' => now() - ] - )->assignRole($adminRole);*/ $adminEmail = env('ADMIN_EMAIL', 'admin@example.com'); $user = User::where('email', $adminEmail)->first(); if ($user) { @@ -75,9 +37,11 @@ class RolePermissionSeeder extends Seeder } else { // Crear solo si no existe User::create([ - 'name' => 'admin', - 'email' => $adminEmail, - 'password' => bcrypt(env('ADMIN_PASSWORD', '12345678')), + 'first_name' => 'Administrador', + 'username' => 'admin', + 'email' => 'admin@example.com', + 'password' => '12345678', + 'is_active' => true, 'email_verified_at' => now() ])->assignRole($adminRole); } diff --git a/package-lock.json b/package-lock.json index ca4863d..aca325d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.7.4", "concurrently": "^9.0.1", "laravel-vite-plugin": "^1.0", + "pdfjs-dist": "^5.2.133", "quill": "^2.0.3", "tailwindcss": "^4.0.7", "vite": "^6.0" @@ -430,6 +431,177 @@ "react": ">= 16" } }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.70.tgz", + "integrity": "sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==", + "optional": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.70", + "@napi-rs/canvas-darwin-arm64": "0.1.70", + "@napi-rs/canvas-darwin-x64": "0.1.70", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.70", + "@napi-rs/canvas-linux-arm64-musl": "0.1.70", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70", + "@napi-rs/canvas-linux-x64-gnu": "0.1.70", + "@napi-rs/canvas-linux-x64-musl": "0.1.70", + "@napi-rs/canvas-win32-x64-msvc": "0.1.70" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz", + "integrity": "sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz", + "integrity": "sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz", + "integrity": "sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz", + "integrity": "sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz", + "integrity": "sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz", + "integrity": "sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz", + "integrity": "sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz", + "integrity": "sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz", + "integrity": "sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.70", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz", + "integrity": "sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.8", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", @@ -1890,6 +2062,17 @@ "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==" }, + "node_modules/pdfjs-dist": { + "version": "5.2.133", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.2.133.tgz", + "integrity": "sha512-abE6ZWDxztt+gGFzfm4bX2ggfxUk9wsDEoFzIJm9LozaY3JdXR7jyLK4Bjs+XLXplCduuWS1wGhPC4tgTn/kzg==", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.67" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/package.json b/package.json index b10ccac..fd03503 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "axios": "^1.7.4", "concurrently": "^9.0.1", "laravel-vite-plugin": "^1.0", + "pdfjs-dist": "^5.2.133", "quill": "^2.0.3", "tailwindcss": "^4.0.7", "vite": "^6.0" diff --git a/resources/css/app.css b/resources/css/app.css index 65ef9c1..ac0a33a 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -83,3 +83,20 @@ select:focus[data-flux-control] { .btn-primary { @apply inline-flex items-center px-4 py-2 bg-blue-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-blue-700 active:bg-blue-900 focus:outline-none focus:border-blue-900 focus:ring focus:ring-blue-300 disabled:opacity-25 transition; } + +.modal-enter { + opacity: 0; +} +.modal-enter-active { + opacity: 1; + transition: opacity 200ms; +} +.modal-exit { + opacity: 1; +} +.modal-exit-active { + opacity: 0; + transition: opacity 200ms; +} + + diff --git a/resources/views/components/app-logo.blade.php b/resources/views/components/app-logo.blade.php index 69fd1af..72b3e91 100644 --- a/resources/views/components/app-logo.blade.php +++ b/resources/views/components/app-logo.blade.php @@ -2,5 +2,5 @@
- Laravel Starter Kit + {{ config('app.name') }}
diff --git a/resources/views/components/comment-item.blade.php b/resources/views/components/comment-item.blade.php new file mode 100644 index 0000000..574167d --- /dev/null +++ b/resources/views/components/comment-item.blade.php @@ -0,0 +1,23 @@ +@props(['comment', 'document']) + +
+
+
+
{{ $comment->user->name }}
+
{{ $comment->created_at->diffForHumans() }}
+

{{ $comment->content }}

+
+ Página {{ $comment->page }} - ({{ number_format($comment->x*100, 1) }}%, {{ number_format($comment->y*100, 1) }}%) +
+
+
+ + @if($comment->children->isNotEmpty()) +
+ @foreach($comment->children as $child) + + @endforeach +
+ @endif +
\ No newline at end of file diff --git a/resources/views/components/folder-item.blade.php b/resources/views/components/folder-item.blade.php index 0816752..7ed592d 100644 --- a/resources/views/components/folder-item.blade.php +++ b/resources/views/components/folder-item.blade.php @@ -7,12 +7,12 @@ - + {{ $folder->name }} diff --git a/resources/views/components/folder-tree.blade.php b/resources/views/components/folder-tree.blade.php index a32b02c..c125da2 100644 --- a/resources/views/components/folder-tree.blade.php +++ b/resources/views/components/folder-tree.blade.php @@ -5,12 +5,12 @@
- + {{ $folder->name }}
diff --git a/resources/views/components/icons.blade.php b/resources/views/components/icons.blade.php index 39e6997..037e759 100644 --- a/resources/views/components/icons.blade.php +++ b/resources/views/components/icons.blade.php @@ -25,6 +25,12 @@ +@elseif($icon === 'chevron-right') + + + + + @elseif($icon === 'user') diff --git a/resources/views/components/icons/excel.blade.php b/resources/views/components/icons/excel.blade.php new file mode 100644 index 0000000..6dbe6f7 --- /dev/null +++ b/resources/views/components/icons/excel.blade.php @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/views/components/icons/pdf.blade.php b/resources/views/components/icons/pdf.blade.php new file mode 100644 index 0000000..d495746 --- /dev/null +++ b/resources/views/components/icons/pdf.blade.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/views/components/layouts/app.blade.php b/resources/views/components/layouts/app.blade.php index 3d15107..6b5fb1b 100644 --- a/resources/views/components/layouts/app.blade.php +++ b/resources/views/components/layouts/app.blade.php @@ -1,5 +1,5 @@ - + {{ $slot }} - + diff --git a/resources/views/components/layouts/app/header.blade.php b/resources/views/components/layouts/app/header.blade.php index cd71e82..ebafe69 100644 --- a/resources/views/components/layouts/app/header.blade.php +++ b/resources/views/components/layouts/app/header.blade.php @@ -16,6 +16,12 @@ {{ __('Dashboard') }} + + + + {{ __('User') }} + + diff --git a/resources/views/components/layouts/documents.blade.php b/resources/views/components/layouts/documents.blade.php new file mode 100644 index 0000000..9709785 --- /dev/null +++ b/resources/views/components/layouts/documents.blade.php @@ -0,0 +1,6 @@ + + + documents layout + {{ $slot }} + + diff --git a/resources/views/components/layouts/livewire-app.blade.php b/resources/views/components/layouts/livewire-app.blade.php.back similarity index 100% rename from resources/views/components/layouts/livewire-app.blade.php rename to resources/views/components/layouts/livewire-app.blade.php.back diff --git a/resources/views/components/property-item.blade.php b/resources/views/components/property-item.blade.php new file mode 100644 index 0000000..2a41a7b --- /dev/null +++ b/resources/views/components/property-item.blade.php @@ -0,0 +1,6 @@ +@props(['label', 'value']) + +
+ {{ $label }}: + {{ $value }} +
\ No newline at end of file diff --git a/resources/views/components/tab-button.blade.php b/resources/views/components/tab-button.blade.php new file mode 100644 index 0000000..9b45c07 --- /dev/null +++ b/resources/views/components/tab-button.blade.php @@ -0,0 +1,11 @@ +@props(['active' => false]) + + \ No newline at end of file diff --git a/resources/views/components/tab-content.blade.php b/resources/views/components/tab-content.blade.php new file mode 100644 index 0000000..fe7e456 --- /dev/null +++ b/resources/views/components/tab-content.blade.php @@ -0,0 +1,5 @@ +@props(['show' => false]) + +
+ {{ $slot }} +
\ No newline at end of file diff --git a/resources/views/components/version-item.blade.php b/resources/views/components/version-item.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 40f9d69..24e56c8 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -28,8 +28,10 @@ Documentos + @can('view roles') + {{ __('Roles') }} @endcan @@ -168,7 +170,7 @@
- div class="bg-purple-500 rounded-full h-2" +
diff --git a/resources/views/documents/show.blade.php b/resources/views/documents/show.blade.php new file mode 100644 index 0000000..21655d9 --- /dev/null +++ b/resources/views/documents/show.blade.php @@ -0,0 +1,288 @@ + +
+ + +
+ + + + + + + +
+
+ + Página de + +
+
+ Zoom: + +
+
+ + +
+ + + + + + +
+
+ + +
+
+
+
+ + +
+ +
+ +
+ + +
+ +
+
+ + + + + + +
+
+ + +
+ implementar +
+ + +
+ +
+ + +
+
+ @foreach($document->versions as $version) + + @endforeach +
+
+ + +
+ implementar +
+
+
+
+
+ + + + diff --git a/resources/views/livewire/country-select.blade.php b/resources/views/livewire/country-select.blade.php new file mode 100644 index 0000000..32df4fd --- /dev/null +++ b/resources/views/livewire/country-select.blade.php @@ -0,0 +1,63 @@ + +
+ + + + +
+ +
+ +
+ + +
+ @forelse($countries as $code => $name) + + @empty +
+ No se encontraron resultados +
+ @endforelse +
+
+ + + +
\ No newline at end of file diff --git a/resources/views/livewire/image-uploader.blade.php b/resources/views/livewire/image-uploader.blade.php new file mode 100644 index 0000000..945092f --- /dev/null +++ b/resources/views/livewire/image-uploader.blade.php @@ -0,0 +1,57 @@ +
+ +
+ + + +
+ + +
+
+ Cargando... +
+

Subiendo imagen...

+
+ +
+ @if(!$imagePath && !$image) + +

Arrastra y suelta tu imagen aquí

+

o haz clic para seleccionar

+ + + + @endif + + +
+
+ + @error('image') +
{{ $message }}
+ @enderror +
+
\ No newline at end of file diff --git a/resources/views/livewire/project-show.blade.php b/resources/views/livewire/project-show.blade.php deleted file mode 100644 index 6ff3630..0000000 --- a/resources/views/livewire/project-show.blade.php +++ /dev/null @@ -1,118 +0,0 @@ -
- -
-
-
- -

{{ $project->name }}

-
- - Editar Proyecto - -
-
- - -
- -
-
-
-

Carpetas

- -
- -
-
-
    - @foreach($project->rootFolders as $folder) - - @endforeach -
-
-
- - -
-
-
-
-

Documentos

- - -
-
-
- - - - - - - - - - - @forelse($this->documents as $document) - - - - - - - @empty - - - - @endforelse - -
NombreVersionesÚltima ActualizaciónEstado
-
- - {{ $document->name }} -
-
- {{ $document->versions_count }} - - {{ $document->updated_at->diffForHumans() }} - - -
- No se encontraron documentos -
-
-
-
-
-
\ No newline at end of file diff --git a/resources/views/livewire/project/show.blade.php b/resources/views/livewire/project/show.blade.php new file mode 100644 index 0000000..c96c409 --- /dev/null +++ b/resources/views/livewire/project/show.blade.php @@ -0,0 +1,283 @@ +
+ +
+
+
+ +

{{ $project->name }}

+
+ + Editar Proyecto + +
+
+ +
+ Subiendo archivos... +
+ + +
+ +
+
+ + + + +
+ +
+
+
+ + +
+ +
+
+
    + @foreach($project->rootFolders as $folder) + + @endforeach +
+
+
+ + +
+
+ + + + + + + + + + + @forelse($this->documents as $document) + + + + + + + @empty + + + + @endforelse + +
NombreVersionesÚltima ActualizaciónEstado
+ + + {{ $document->versions_count }} + + {{ $document->updated_at->diffForHumans() }} + + +
+ No se encontraron documentos en esta carpeta +
+
+
+
+
+ + + @if($showFolderModal) +
+
+
+

Crear nueva carpeta

+

Ingresa el nombre de la nueva carpeta

+
+ +
+ + @error('folderName') +

{{ $message }}

+ @enderror +
+ +
+ + +
+
+
+ @endif + + + @if($showUploadModal) +
+ +
+
+

Subir archivos

+

+ {{ $currentFolder ? "Carpeta destino: {$currentFolder->name}" : "Carpeta raíz del proyecto" }} +

+
+ + +
+ + + + + + +
+

+ Arrastra archivos aquí o haz clic para seleccionar +

+

+ Formatos soportados: PDF, DOCX, XLSX, JPG, PNG (Máx. 10MB) +

+
+
+ + +
+ @if(count($selectedFiles) > 0) +
    + @foreach($selectedFiles as $index => $file) +
  • +
    + + + {{ $file->getClientOriginalName() }} + +
    + +
    + + {{ number_format($file->getSize() / 1024, 2) }} KB + + +
    +
  • + @endforeach +
+ @endif +
+ + +
+ + +
+
+
+ @endif +
+ + \ No newline at end of file diff --git a/resources/views/livewire/settings/profile.blade.php b/resources/views/livewire/settings/profile.blade.php index cb08833..fd25295 100644 --- a/resources/views/livewire/settings/profile.blade.php +++ b/resources/views/livewire/settings/profile.blade.php @@ -15,7 +15,7 @@ new class extends Component { */ public function mount(): void { - $this->name = Auth::user()->name; + $this->name = Auth::user()->first_name; $this->email = Auth::user()->email; } diff --git a/resources/views/livewire/user-photo-upload.blade.php b/resources/views/livewire/user-photo-upload.blade.php new file mode 100644 index 0000000..5d70206 --- /dev/null +++ b/resources/views/livewire/user-photo-upload.blade.php @@ -0,0 +1,96 @@ + + +
+ + @if($existingPhoto) +
+ Foto actual + + +
+ @endif + + +
+ + + + +
+ + + @if($photo || $tempPhoto) +
+ +
+ @endif + + + @error('photo') +
{{ $message }}
+ @enderror +
\ No newline at end of file diff --git a/resources/views/livewire/user-table.blade.php b/resources/views/livewire/user-table.blade.php new file mode 100644 index 0000000..a525006 --- /dev/null +++ b/resources/views/livewire/user-table.blade.php @@ -0,0 +1,193 @@ +
+ +
+
+ + +
+ @foreach($available_columns as $key => $label) + + @endforeach +
+
+
+ + +
+ + + + @foreach($available_columns as $key => $label) + @if($columns[$key]) + @if($key !== 'is_active') + + @else + + @endif + @endif + @endforeach + + + + + + + @foreach($users as $user) + + @if($columns['full_name']) + + @endif + + + + + + + + + + + + + + + + @endforeach + +
+
+
+ {{ $label }} + +
+ @if($sortField === $key) + + @if($sortDirection === 'asc') + ↑ + @else + ↓ + @endif + + @endif +
+
+
+ Estado + +
+
Acciones
+ + + @if($user->profile_photo_path) +
+ {{ $user->full_name }} +
+ @else +
+ + + +
+ @endif + + + + {{ $user->full_name }} + @if(!$user->is_active) + (Inactivo) + @endif + +
+
+ @if($columns['username']) + {{ $user->username }} + @else + N/A + @endif + + @if($columns['email']) + +
+ + + + {{ $user->email }} +
+ @else + N/A + @endif +
+ @if($columns['phone']) + +
+ + + + {{ $user->phone ?? 'N/A' }} +
+ @else + N/A + @endif +
+ @if($columns['access_start']) + {{ $user->access_start?->format('d/m/Y') }} + @else + N/A + @endif + + @if($columns['created_at']) + {{ $user->created_at->format('d/m/Y H:i') }} + @else + N/A + @endif + + @if($columns['is_active']) + {{ $user->is_active ? 'Activo' : 'Inactivo' }} + @else + N/A + @endif + + +
+ + + + + + + + + +
+
+
+ + +
+ {{ $users->links() }} +
+
\ No newline at end of file diff --git a/resources/views/projects/create.blade.php b/resources/views/projects/create.blade.php index 4bca5d0..c1ec1ab 100644 --- a/resources/views/projects/create.blade.php +++ b/resources/views/projects/create.blade.php @@ -1,360 +1,378 @@ + - -

- - - - +
+
+
+

+ + + + + {{ __('Nuevo Proyecto') }} + + @error('error') +

{{ $message }}

+ @enderror + +

+
+
+ + +
+ @csrf - {{ __('Nuevo Proyecto') }} -

-
- -
-
-

- - - - {{ __('Nuevo Proyecto') }} -

-
-
- -
-
-
-
- - @csrf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - @error('name') -

{{ $message }}

- @enderror -
- - - - {{ old('description') }} - - @error('description') -

{{ $message }}

- @enderror -
- - -
- -
- - - - - - - - -
- - - -
{!! old('description') !!}
-
- @error('description') -

{{ $message }}

- @enderror -
- - - - - - - @error('status') -

{{ $message }}

- @enderror -
- - -
- - -
- @error('project_image') -

{{ $message }}

- @enderror -
- - -
-
- - @error('address') -

{{ $message }}

- @enderror -
- -
- - @error('postal_code') -

{{ $message }}

- @enderror -
- -
- - @error('province') -

{{ $message }}

- @enderror -
- -
- - - @foreach(config('countries') as $code => $name) - - @endforeach - - @error('country') -

{{ $message }}

- @enderror -
-
-
- - -
-
-
- - -
-
- - -
-
- @error('latitude') -

{{ $message }}

- @enderror - @error('longitude') -

{{ $message }}

- @enderror -
- - -
-
- -
- -
-
- -
- - -
-
-
- - -
- de - - a - -
-
- - -
-
-
- - - - - -
-
-
-
- - -
- @foreach($users as $user) - - @endforeach -
- @error('team') -

{{ $message }}

- @enderror -
- - -
- - {{ __('Cancelar') }} - - - - {{ __('Crear Proyecto') }} - -
- + +
+
+
+
+
+ Datos Generales
-
-
- @push('styles') +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + @error('name') +

{{ $message }}

+ @enderror +
+ + + + {{ old('description') }} + + @error('description') +

{{ $message }}

+ @enderror +
+ + + + + + + @error('status') +

{{ $message }}

+ @enderror +
+ + +
+ de + + a + +
+
+
+ +
+
+
+
+
+ Ubicación +
+
+ +
+ + + + + + + + + + + + + + +
+ + +
+
+ + + @error('address') +

{{ $message }}

+ @enderror +
+ +
+ + @error('postal_code') +

{{ $message }}

+ @enderror +
+ +
+ + @error('province') +

{{ $message }}

+ @enderror +
+ +
+ + @error('country') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ @error('latitude') +

{{ $message }}

+ @enderror + @error('longitude') +

{{ $message }}

+ @enderror +
+
+ +
+
+
+
+
+ Otros datos +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ @error('project_image') +

{{ $message }}

+ @enderror +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+
+ + +
+
+
+ + + + + +
+
+
+
+ + +
+ @foreach($users as $user) + + @endforeach +
+ @error('team') +

{{ $message }}

+ @enderror +
+
+ + +
+ + {{ __('Cancelar') }} + + + + {{ __('Crear Proyecto') }} + +
+ + +
+ @@ -363,11 +381,7 @@ integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> - @endpush - - - @push('scripts') @@ -469,5 +483,4 @@ }; - @endpush \ No newline at end of file diff --git a/resources/views/projects/create.blade.php.back b/resources/views/projects/create.blade.php.back deleted file mode 100644 index 228fb61..0000000 --- a/resources/views/projects/create.blade.php.back +++ /dev/null @@ -1,386 +0,0 @@ - - - -

- Nuevo Proyecto -

-
- -
-
-
-
-
- @csrf - -
- -
- -
- - - @error('name') -

{{ $message }}

- @enderror -
- - -
- - - {{ old('description') }} - - @error('description') -

{{ $message }}

- @enderror -
- - -
- -
- -
- - - - - - - - - -
- - -
{!! old('description') !!}
-
- @error('description') -

{{ $message }}

- @enderror -
- - -
- - - - - - @error('status') -

{{ $message }}

- @enderror -
- - -
- -
- - -
- @error('project_image') -

{{ $message }}

- @enderror -
-
- - -
- -
- -
-
- - @error('address') -

{{ $message }}

- @enderror -
- -
- - @error('postal_code') -

{{ $message }}

- @enderror -
- -
- - @error('province') -

{{ $message }}

- @enderror -
- -
- - - @foreach(config('countries') as $code => $name) - - @endforeach - - @error('country') -

{{ $message }}

- @enderror -
-
-
- - -
- -
-
-
- - -
-
- - -
-
- @error('latitude') -

{{ $message }}

- @enderror - @error('longitude') -

{{ $message }}

- @enderror -
-
-
- - -
- -
- -
- -
-
- -
- -
-
- -
- - -
-
-
- - -
-
- - -
-
- - -
-
-
- - -
- -
- -
-
-
- - - - - -
-
-
-
- - - -
-
- - - - - -
- Miembros del Equipo - -
- @foreach($users as $user) - - @endforeach -
- @error('team') -

{{ $message }}

- @enderror -
- - -
- - {{ __('Cancelar') }} - - - - {{ __('Crear Proyecto') }} - -
-
-
-
-
-
- - @push('styles') - - - @endpush - - @push('scripts') - - - - - @endpush -
\ No newline at end of file diff --git a/resources/views/projects/index.blade.php b/resources/views/projects/index.blade.php index 5117161..3c402e4 100644 --- a/resources/views/projects/index.blade.php +++ b/resources/views/projects/index.blade.php @@ -109,11 +109,7 @@
- @if($projects->hasPages()) -
- {{ $projects->withQueryString()->links() }} -
- @endif +
\ No newline at end of file diff --git a/resources/views/projects/show.blade.php b/resources/views/projects/show.blade.php new file mode 100644 index 0000000..f88cffe --- /dev/null +++ b/resources/views/projects/show.blade.php @@ -0,0 +1,30 @@ + +
+ +
+ +
+ + + javi + + + + + + + +
+ +
\ No newline at end of file diff --git a/resources/views/projects/show.blade.php.back b/resources/views/projects/show.blade.php.back deleted file mode 100644 index a292e03..0000000 --- a/resources/views/projects/show.blade.php.back +++ /dev/null @@ -1,48 +0,0 @@ -@extends('layouts.app') - -@section('content') -
- -
- -
- - - - - - - - - @livewire('folder.create-modal', ['project' => $project, 'parentFolder' => $currentFolder ?? null]) - @livewire('document.upload-modal', ['project' => $project, 'currentFolder' => $currentFolder ?? null]) -
-@endsection \ No newline at end of file diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php new file mode 100644 index 0000000..b35c681 --- /dev/null +++ b/resources/views/users/create.blade.php @@ -0,0 +1,351 @@ + +
+ +
+
+ + + +

+ {{ isset($user) ? 'Editar Usuario' : 'Nuevo Usuario' }} +

+
+

+ @isset($user) + Modifique los campos necesarios para actualizar la información del usuario. + @else + Complete todos los campos obligatorios para registrar un nuevo usuario en el sistema. + @endisset +

+
+ + + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+ @csrf + @isset($user) + @method('PUT') + @endisset + + +
+
+
+
+
+ Datos Personales +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+ + +
+
+
+
+
+ Configuración de acceso +
+
+ + +
+ + + + + + + + + + + + + + +
+ + +
+ de + + a + +
+
+ + +
+ + +
+ +
+
+ + +
+
+
+
+
+ Datos de contacto +
+
+ + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+
+ + +
+ + + + +
+
+ + +
+ + + + + +
+
+
+ + +
+
+
+
+
+ Otros datos +
+
+ + +
+ + + + + + + + + + + + + php + + +
+ + +
+ +
+
+ + +
+ @livewire('image-uploader', [ + 'fieldName' => 'image_path', // Nombre del campo en la BD + 'label' => 'Imagen principal' // Etiqueta personalizada + ]) + + +
+
+
+ + +
+ +
+
+
+ + + + +
\ No newline at end of file diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php new file mode 100644 index 0000000..1d949ab --- /dev/null +++ b/resources/views/users/index.blade.php @@ -0,0 +1,17 @@ + +
+
+
+ + + +

Usuarios

+
+ + Nuevo + +
+ + @livewire('user-table') +
+
\ No newline at end of file diff --git a/resources/views/users/show.blade.php b/resources/views/users/show.blade.php new file mode 100644 index 0000000..c62b1ac --- /dev/null +++ b/resources/views/users/show.blade.php @@ -0,0 +1,321 @@ + + +
+ +
+ +
+ + + +
+

+ {{ $user->first_name }} {{ $user->last_name }} +

+ + +
+
+

+ {{ $user->first_name }} +

+
+ + + + @if($user->phone) + + @endif +
+
+
+ + +
+ +
+ + + + + + + @if($previousUser) + + + + + + @endif + + @if($nextUser) + + + + + + @endif +
+ + + + {{ $user->is_active ? 'Activo' : 'Inactivo' }} + +
+
+ + +
+ +
+ +
+ + +
+ +
+
+ + + @foreach(['name' => 'Nombre', 'last_name' => 'Apellido', 'email' => 'Email', 'phone' => 'Teléfono', 'created_at' => 'Fecha Registro'] as $field => $label) + + + + + @endforeach + +
{{ $label }} + {{ $user->$field ?? 'N/A' }} +
+
+ + +
+ + + + + Editar + + + {{-- Formulario de Edición --}} +
+ @csrf + @method('PUT') + +
+ + {{-- Formulario de Eliminación --}} +
+ @csrf + @method('DELETE') + +
+
+
+ + +
+ @foreach($permissionGroups as $groupName => $group) +
+ +
+

+ {{ ucfirst($groupName) }} Permissions +

+ +
+ + + + + + + + +
+
+ + +
+ @foreach($group['permissions'] as $permission) +
+
+ {{ $permission['description'] ?? $permission['name'] }} + + @if($permission['description']) + ({{ $permission['name'] }}) + @endif +
+ + +
+ @endforeach +
+
+ @endforeach +
+
+ + +
+
+ +
+

Proyectos Asociados

+ +
+ falta implementar +
+
+ + + +
+
+
+
+ + + + + + +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index c4a1959..a6efbb5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,18 +34,30 @@ require __DIR__.'/auth.php'; Route::middleware(['auth', 'verified'])->group(function () { // Dashboard Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); + + // Usuarios: + Route::get('users/{user}/edit', [UserController::class, 'edit'])->name('users.edit'); + Route::get('/users/create', [UserController::class, 'create'])->name('users.create')->middleware('can:create-users'); + // Procesar creación de usuario + Route::post('/users', [UserController::class, 'store'])->name('users.store')->middleware('can:create-users'); + Route::put('/usuarios/{user}', [UserController::class, 'update'])->name('users.update')->middleware('can:update,user'); // Opcional: si usas políticas + Route::post('/users', [UserController::class, 'store'])->name('users.store'); + Route::put('users/{user}', [UserController::class, 'update'])->name('users.update'); + Route::patch('users/{user}', [UserController::class, 'update']); + Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy'); + Route::get('users/{user}', [UserController::class, 'show'])->name('users.show'); // Proyectos Route::resource('projects', ProjectController::class); - Route::get('/projects/{project}', ProjectController::class)->name('projects.show'); - Route::get('/projects/{project}', ProjectController::class)->name('projects.show')->middleware('can:view,project'); // Opcional: política de acceso - Route::get('/projects/{project}', ProjectShow::class)->name('projects.show'); - + //Route::get('/projects/{project}', ProjectController::class)->name('projects.show'); + //Route::get('/projects/{project}', ProjectController::class)->name('projects.show')->middleware('can:view,project'); // Opcional: política de acceso + Route::get('/projects/{project}', ProjectShow::class)->name('projects.show')->middleware('auth'); // Documentos Route::resource('documents', DocumentController::class); - Route::post('/documents/{document}/approve', [DocumentController::class, 'approve'])->name('documents.approve'); - Route::post('/documents/{document}/comment', [DocumentController::class, 'addComment'])->name('documents.comment'); + Route::post('/documents/{document}', [DocumentController::class, 'show'])->name('documents.show'); + //Route::post('/documents/{document}/approve', [DocumentController::class, 'approve'])->name('documents.approve'); + //Route::post('/documents/{document}/comment', [DocumentController::class, 'addComment'])->name('documents.comment'); // Carpetas Route::prefix('folders')->group(function () { diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/storage/app/public/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore