mejoras
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
@props(['folder', 'currentFolder', 'expandedFolders', 'level' => 0])
|
||||
|
||||
@props(['folder', 'currentFolder', 'expandedFolders', 'level' => 0, 'itemsCount' => 0])
|
||||
<li class="pl-{{ $level * 4 }}">
|
||||
<div class="flex items-center justify-between p-2 hover:bg-gray-50
|
||||
{{ $folder->id === optional($currentFolder)->id ? 'bg-blue-50' : '' }}">
|
||||
<div class="flex items-center flex-1" wire:click="selectFolder({{ $folder->id }})">
|
||||
<div class="flex items-center relative flex-1" wire:click="selectFolder({{ $folder->id }})">
|
||||
<button wire:click.prevent="toggleFolder({{ $folder->id }})"
|
||||
class="mr-2">
|
||||
@if(in_array($folder->id, $expandedFolders))
|
||||
@@ -12,11 +11,13 @@
|
||||
<x-icons icon="chevron-right" class="w-4 h-4" />
|
||||
@endif
|
||||
</button>
|
||||
<x-icons icon="folder" class="w-5 h-5 mr-2 text-yellow-500" />
|
||||
<span>{{ $folder->name }}</span>
|
||||
<flux:icon.folder class="size-5 mr-2 text-yellow-500"/>
|
||||
<span class="flex-1 whitespace-nowrap">{{ $folder->name }}</span>
|
||||
@if($itemsCount > 0)
|
||||
<flux:badge class="text-xs font-medium rounded-sm px-1 py-0.5 text-gray-700 dark:text-gray-200 bg-gray-400/15 dark:bg-white/10" size="sm" inset="top bottom">{{ $itemsCount }}</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(in_array($folder->id, $expandedFolders))
|
||||
<ul class="ml-4">
|
||||
@foreach($folder->children as $child)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<div class="flex items-center flex-1" wire:click="selectFolder({{ $folder->id }})">
|
||||
<button wire:click.prevent="toggleFolder({{ $folder->id }})" class="mr-2">
|
||||
@if(in_array($folder->id, $expandedFolders))
|
||||
<x-icons icon="chevron-down" class="w-4 h-4 text-gray-400" />
|
||||
<flux:icon.chevron-down class="size-4 text-gray-400" />
|
||||
@else
|
||||
<x-icons icon="chevron-right" class="w-4 h-4 text-gray-400" />
|
||||
<flux:icon.chevron-right class="size-4 text-gray-400" />
|
||||
@endif
|
||||
</button>
|
||||
<x-icons icon="folder" class="w-5 h-5 mr-2 text-yellow-500" />
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<x-layouts.app.header :title="$title ?? null">
|
||||
<x-layouts.app.header
|
||||
:title="$title ?? null"
|
||||
:show-sidebar="$showSidebar ?? false"
|
||||
>
|
||||
|
||||
<flux:main>
|
||||
{{ $slot }}
|
||||
</flux:main>
|
||||
|
||||
@@ -12,15 +12,14 @@
|
||||
</a>
|
||||
|
||||
<flux:navbar class="-mb-px max-lg:hidden">
|
||||
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
<flux:navbar.item icon="home" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Inicio') }}
|
||||
</flux:navbar.item>
|
||||
</flux:navbar>
|
||||
|
||||
<flux:navbar class="-mb-px max-lg:hidden">
|
||||
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('User') }}
|
||||
|
||||
<flux:navbar.item icon="link" :href="route('projects.index')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Projects') }}
|
||||
</flux:navbar.item>
|
||||
|
||||
</flux:navbar>
|
||||
|
||||
<flux:spacer />
|
||||
@@ -123,8 +122,24 @@
|
||||
</flux:navlist>
|
||||
</flux:sidebar>
|
||||
|
||||
{{ $slot }}
|
||||
<!-- Contenedor principal con sidebar condicional -->
|
||||
<div class="flex flex-1">
|
||||
@if($showSidebar)
|
||||
<!-- Desktop Sidebar -->
|
||||
<flux:sidebar class="hidden lg:block w-64 shrink-0 border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
{{ $sidebar ?? '' }} <!-- Aquí se inyectará el contenido -->
|
||||
@stack('sidebar-menu')
|
||||
</flux:sidebar>
|
||||
@endif
|
||||
|
||||
<!-- Contenido principal -->
|
||||
<main class="flex-1 overflow-auto">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
@fluxScripts
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,183 +1,141 @@
|
||||
<!-- resources/views/layouts/app.blade.php -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<!-- Cabecera estándar de Laravel -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div x-data="{ sidebarOpen: false }" class="flex h-screen">
|
||||
<!-- Sidebar -->
|
||||
<aside class="bg-gray-800 text-white w-64 flex-shrink-0">
|
||||
<div class="p-4">
|
||||
<h1 class="text-2xl font-bold">mDMS</h1>
|
||||
</div>
|
||||
|
||||
<nav class="mt-6">
|
||||
<x-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
|
||||
<x-icons icon="dashboard" class="w-5 h-5 mr-3" />
|
||||
Dashboard
|
||||
</x-nav-link>
|
||||
|
||||
<x-nav-link href="{{ route('projects.index') }}" :active="request()->routeIs('projects.*')">
|
||||
<x-icons icon="folder" class="w-5 h-5 mr-3" />
|
||||
Proyectos
|
||||
</x-nav-link>
|
||||
|
||||
<x-nav-link href="{{ route('documents.index') }}" :active="request()->routeIs('documents.*')">
|
||||
<x-icons icon="document" class="w-5 h-5 mr-3" />
|
||||
Documentos
|
||||
</x-nav-link>
|
||||
<x-layouts.app :title="__('Home')" :showSidebar={{ $showSidebar }}>
|
||||
<!-- Estadísticas rápidas -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<x-stats-card title="Proyectos" value=" {{ $stats['projects_count'] }} " icon="folder" color="bg-blue-500"/>
|
||||
<x-stats-card title="Documentos" value=" {{ $stats['documents_count'] }} " icon="document" color="bg-green-500" />
|
||||
<x-stats-card title="Almacenamiento" value=" {{$stats['users_count'] }} " icon="storage" color="bg-purple-500" />
|
||||
<x-stats-card title="Pendientes" value=" {{ $stats['storage_used'] }} " icon="clock" color="bg-yellow-500" />
|
||||
</div>
|
||||
|
||||
@can('view roles')
|
||||
<x-nav-link :href="route('roles.index')" :active="request()->routeIs('roles.*')">
|
||||
<x-icons icon="roles" class="w-5 h-5 mr-3" />
|
||||
{{ __('Roles') }}
|
||||
</x-nav-link>
|
||||
@endcan
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Contenido principal -->
|
||||
<main class="flex-1 overflow-x-hidden overflow-y-auto">
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow">
|
||||
<div class="flex items-center justify-between px-6 py-4">
|
||||
<div class="flex-1 max-w-2xl">
|
||||
<div class="relative">
|
||||
<input type="search" placeholder="Buscar documentos..."
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg border focus:outline-none focus:border-blue-500">
|
||||
<x-icons icon="search" class="w-5 h-5 absolute left-3 top-2.5 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<x-dropdown align="right">
|
||||
<x-slot name="trigger">
|
||||
<button class="flex items-center space-x-2">
|
||||
<span class="text-gray-700">{{ Auth::user()->name }}</span>
|
||||
<x-icons icon="chevron-down" class="w-4 h-4" />
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-dropdown-link href="{{ route('profile.show') }}">
|
||||
<x-icons icon="user" class="w-5 h-5 mr-2" /> Perfil
|
||||
</x-dropdown-link>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<x-dropdown-link href="{{ route('logout') }}"
|
||||
onclick="event.preventDefault(); this.closest('form').submit();">
|
||||
<x-icons icon="logout" class="w-5 h-5 mr-2" /> Cerrar sesión
|
||||
</x-dropdown-link>
|
||||
</form>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Contenido del dashboard -->
|
||||
<div class="p-6">
|
||||
<!-- Estadísticas rápidas -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<x-stats-card title="Proyectos" value=" {{ $stats['projects_count'] }} " icon="folder" color="bg-blue-500"/>
|
||||
<x-stats-card title="Documentos" value=" {{ $stats['documents_count'] }} " icon="document" color="bg-green-500" />
|
||||
<x-stats-card title="Almacenamiento" value=" {{$stats['users_count'] }} " icon="storage" color="bg-purple-500" />
|
||||
<x-stats-card title="Pendientes" value=" {{ $stats['storage_used'] }} " icon="clock" color="bg-yellow-500" />
|
||||
</div>
|
||||
|
||||
<!-- Documentos recientes -->
|
||||
<div class="bg-white rounded-lg shadow mb-8">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Documentos recientes</h3>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nombre</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Tipo</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Estado</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($recentDocuments as $document)
|
||||
<tr>
|
||||
<td class="px-6 py-4">{{ $document->name }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
{{ $document->type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<x-status-badge :status="$document->status" />
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<x-dropdown>
|
||||
<x-dropdown-link href="{{ route('documents.show', $document) }}">
|
||||
<x-icons icon="eye" class="w-4 h-4 mr-2" /> Ver
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="#">
|
||||
<x-icons icon="download" class="w-4 h-4 mr-2" /> Descargar
|
||||
</x-dropdown-link>
|
||||
</x-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actividad reciente -->
|
||||
<div class="grid lg:grid-cols-2 gap-6">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Actividad reciente</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flow-root">
|
||||
<ul class="-mb-4">
|
||||
@foreach($recentActivities as $activity)
|
||||
<li class="mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<x-activity-icon :type="$activity->type" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-900">{{ $activity->description }}</p>
|
||||
<p class="text-xs text-gray-500">{{ $activity->created_at->diffForHumans() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Uso de almacenamiento -->
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Uso de almacenamiento</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ $stats['storage_used'] }}</p>
|
||||
<p class="text-sm text-gray-500">de {{ $stats['storage_limit'] }} disponibles</p>
|
||||
</div>
|
||||
<x-icons icon="storage" class="w-12 h-12 text-purple-500" />
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="bg-purple-500 rounded-full h-2"
|
||||
style="width: {{ $stats['storage_percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- Documentos recientes -->
|
||||
<div class="bg-white rounded-lg shadow mb-8">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Documentos recientes</h3>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nombre</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Tipo</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Estado</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach($recentDocuments as $document)
|
||||
@if(!$document)
|
||||
@continue
|
||||
@endif
|
||||
<tr>
|
||||
<td class="px-6 py-4">{{ $document->name }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
{{ $document->type }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<x-status-badge :status="$document->status" />
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<x-dropdown>
|
||||
<x-dropdown-link href="{{ route('documents.show', $document) }}">
|
||||
<x-icons icon="eye" class="w-4 h-4 mr-2" /> Ver
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link href="#">
|
||||
<x-icons icon="download" class="w-4 h-4 mr-2" /> Descargar
|
||||
</x-dropdown-link>
|
||||
</x-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actividad reciente -->
|
||||
<div class="grid lg:grid-cols-2 gap-6">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Actividad reciente</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flow-root">
|
||||
<ul class="-mb-4">
|
||||
@foreach($recentActivities as $activity)
|
||||
<li class="mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<x-icons icon="activity" class="w-8 h-8 text-blue-500" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-gray-900">{{ $activity->description }}</p>
|
||||
<p class="text-xs text-gray-500">{{ $activity->created_at }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Uso de almacenamiento -->
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="px-6 py-4 border-b">
|
||||
<h3 class="text-lg font-semibold">Uso de almacenamiento</h3>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ $stats['storage_used'] }}</p>
|
||||
<p class="text-sm text-gray-500">de {{ $stats['storage_limit'] }} disponibles</p>
|
||||
</div>
|
||||
<x-icons icon="storage" class="w-12 h-12 text-purple-500" />
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="bg-purple-500 rounded-full h-2"
|
||||
style="width: {{ $stats['storage_percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.item
|
||||
icon="computer-desktop"
|
||||
:href="route('dashboard')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Panel de control') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:separator />
|
||||
|
||||
<!-- Sección de Usuarios -->
|
||||
<flux:navlist.group :heading="__('User')" expandable>
|
||||
<flux:navlist.item
|
||||
icon="users"
|
||||
:href="route('users.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Users') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="user-plus"
|
||||
:href="route('users.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create User') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
</x-layouts.app>
|
||||
File diff suppressed because it is too large
Load Diff
331
resources/views/documents/show.blade.php.back
Normal file
331
resources/views/documents/show.blade.php.back
Normal file
@@ -0,0 +1,331 @@
|
||||
<x-layouts.app :title="__('Show document')">
|
||||
<div class="flex h-screen bg-gray-100"
|
||||
@resize.window.debounce="renderPage(currentPage)">
|
||||
|
||||
<!-- Zona izquierda - Visualizador de documentos -->
|
||||
<div class="w-1/2 bg-gray-800 p-4 relative">
|
||||
<!-- Loading State -->
|
||||
<template x-if="loading">
|
||||
<div class="absolute inset-0 bg-white bg-opacity-90 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<svg class="animate-spin h-12 w-12 text-blue-500 mx-auto mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-600">Cargando documento...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Error State -->
|
||||
<template x-if="error">
|
||||
<div class="absolute inset-0 bg-red-50 flex items-center justify-center p-4">
|
||||
<div class="text-center text-red-600">
|
||||
<svg class="h-12 w-12 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<p x-text="error"></p>
|
||||
<p class="mt-2 text-sm">URL del documento: <span class="break-all" x-text="pdfUrl"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Controles del PDF -->
|
||||
<div class="flex items-center justify-between mb-4 bg-gray-700 p-2 rounded">
|
||||
<div class="flex items-center space-x-4 text-white">
|
||||
<button @click="previousPage" :disabled="currentPage <= 1" class="disabled:opacity-50">
|
||||
<x-icons icon="chevron-left" class="w-6 h-6" />
|
||||
</button>
|
||||
<span>Página <span x-text="currentPage"></span> de <span x-text="totalPages"></span></span>
|
||||
<button @click="nextPage" :disabled="currentPage >= totalPages" class="disabled:opacity-50">
|
||||
<x-icons icon="chevron-right" class="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-white">
|
||||
Zoom:
|
||||
<select x-model="scale" @change="renderPage(currentPage)" class="bg-gray-600 rounded px-2 py-1">
|
||||
<option value="0.25">25%</option>
|
||||
<option value="0.5">50%</option>
|
||||
<option value="0.75">75%</option>
|
||||
<option value="1">100%</option>
|
||||
<option value="1.5">150%</option>
|
||||
<option value="2">200%</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Canvas del PDF con comentarios -->
|
||||
<div class="flex-1 bg-white shadow-lg overflow-auto relative"
|
||||
id="pdf-container"
|
||||
@scroll.debounce="handleScroll">
|
||||
<div class="pdf-page-container" :style="`width: ${pageWidth}px`">
|
||||
<template x-for="(page, index) in renderedPages" :key="index">
|
||||
<div class="pdf-page">
|
||||
<canvas class="pdf-canvas"
|
||||
:id="`canvas-${page.pageNumber}`"></canvas>
|
||||
<div class="text-layer"
|
||||
:id="`text-layer-${page.pageNumber}`"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Marcadores de comentarios existentes -->
|
||||
<template x-for="comment in comments" :key="comment.id">
|
||||
<div
|
||||
class="absolute w-4 h-4 bg-yellow-400 rounded-full cursor-pointer border-2 border-yellow-600"
|
||||
:style="`left: ${comment.x * 100}%; top: ${comment.y * 100}%;`"
|
||||
@click="scrollToComment(comment)"
|
||||
x-tooltip="'Ver comentario'"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Formulario flotante para nuevos comentarios -->
|
||||
<div
|
||||
class="absolute bg-white p-4 rounded-lg shadow-xl w-64"
|
||||
x-show="showCommentForm"
|
||||
:style="`top: ${clickY}px; left: ${clickX}px`"
|
||||
@click.outside="closeCommentForm"
|
||||
>
|
||||
<form @submit.prevent="submitComment">
|
||||
<textarea
|
||||
x-model="commentText"
|
||||
class="w-full mb-2 p-2 border rounded"
|
||||
placeholder="Escribe tu comentario..."
|
||||
rows="3"
|
||||
required
|
||||
></textarea>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary w-full flex items-center justify-center"
|
||||
>
|
||||
<x-icons icon="save" class="w-4 h-4 mr-2" /> Guardar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zona derecha - Tabs de información -->
|
||||
<div x-data="{ activeTab: 'properties' }" class="w-1/2 flex flex-col bg-white">
|
||||
<!-- Tabs de navegación -->
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="flex space-x-4 px-4 pt-2">
|
||||
<button @click="activeTab = 'properties'"
|
||||
:class="activeTab === 'properties' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Propiedades
|
||||
</button>
|
||||
<button @click="activeTab = 'security'"
|
||||
:class="activeTab === 'security' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Seguridad
|
||||
</button>
|
||||
<button @click="activeTab = 'comments'"
|
||||
:class="activeTab === 'comments' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Comentarios
|
||||
</button>
|
||||
<button @click="activeTab = 'history'"
|
||||
:class="activeTab === 'history' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Historial
|
||||
</button>
|
||||
<button @click="activeTab = 'relations'"
|
||||
:class="activeTab === 'relations' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Relaciones
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Contenido de los tabs -->
|
||||
<div class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<!-- Propiedades -->
|
||||
<div x-show="activeTab === 'properties'">
|
||||
<div class="space-y-4">
|
||||
<x-property-item label="Nombre" :value="$document->name" />
|
||||
<x-property-item label="Tipo" :value="$document->type" />
|
||||
<x-property-item label="Tamaño" :value="$document->size_for_humans" />
|
||||
<x-property-item label="Creado por" :value="Storage::url($document->file_path)" />
|
||||
<x-property-item label="Fecha creación" :value="$document->created_at->format('d/m/Y H:i')" />
|
||||
<x-property-item label="Última modificación" :value="$document->updated_at->format('d/m/Y H:i')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Seguridad -->
|
||||
<div x-show="activeTab === 'security'" x-cloak>
|
||||
implementar
|
||||
</div>
|
||||
|
||||
<!-- Comentarios -->
|
||||
<div x-show="activeTab === 'comments'" x-cloak>
|
||||
<template x-for="comment in comments" :key="comment.id">
|
||||
<div class="p-4 mb-2 border rounded hover:bg-gray-50">
|
||||
<div class="text-sm text-gray-500"
|
||||
x-text="`Página ${comment.page} - ${new Date(comment.created_at).toLocaleString()}`"></div>
|
||||
<div x-text="comment.text" class="mt-1"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Historial -->
|
||||
<div x-show="activeTab === 'history'" x-cloak>
|
||||
<div class="space-y-4">
|
||||
@foreach($document->versions as $version)
|
||||
<x-version-item :version="$version" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Relaciones -->
|
||||
<div x-show="activeTab === 'relations'" x-cloak>
|
||||
implementar
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>
|
||||
<script>
|
||||
// Configuración del Visor PDF
|
||||
var currentPage = 1;
|
||||
var nextPage = 2;
|
||||
|
||||
const url = "{{ Storage::url($document->file_path) }}";
|
||||
let pdfDoc = null,
|
||||
pageNum = 1,
|
||||
pageRendering = false,
|
||||
pageNumPending = null,
|
||||
scale = 0.8,
|
||||
canvas = document.getElementById('pdf-canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
function renderPage(num) {
|
||||
pageRendering = true;
|
||||
pdfDoc.getPage(num).then(function(page) {
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
page.render(renderContext).promise.then(() => {
|
||||
pageRendering = false;
|
||||
loadCommentsForPage(num);
|
||||
});
|
||||
|
||||
// Wait for rendering to finish
|
||||
renderTask.promise.then(function() {
|
||||
pageRendering = false;
|
||||
if (pageNumPending !== null) {
|
||||
// New page rendering is pending
|
||||
renderPage(pageNumPending);
|
||||
pageNumPending = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleCanvasClick(event) {
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
this.clickX = event.clientX - rect.left;
|
||||
this.clickY = event.clientY - rect.top;
|
||||
this.showCommentForm = true;
|
||||
}
|
||||
|
||||
// Cargar PDF
|
||||
pdfjsLib.getDocument(url).promise.then(function(pdf) {
|
||||
pdfDoc = pdf;
|
||||
renderPage(pageNum);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* If another page rendering in progress, waits until the rendering is
|
||||
* finised. Otherwise, executes rendering immediately.
|
||||
*/
|
||||
function queueRenderPage(num) {
|
||||
if (pageRendering) {
|
||||
pageNumPending = num;
|
||||
} else {
|
||||
renderPage(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays previous page.
|
||||
*/
|
||||
function onPrevPage() {
|
||||
if (pageNum <= 1) {
|
||||
return;
|
||||
}
|
||||
pageNum--;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById('prev').addEventListener('click', onPrevPage);
|
||||
|
||||
/**
|
||||
* Displays next page.
|
||||
*/
|
||||
function onNextPage() {
|
||||
if (pageNum >= pdfDoc.numPages) {
|
||||
return;
|
||||
}
|
||||
pageNum++;
|
||||
queueRenderPage(pageNum);
|
||||
}
|
||||
document.getElementById('next').addEventListener('click', onNextPage);
|
||||
|
||||
/**
|
||||
* Asynchronously downloads PDF.
|
||||
*/
|
||||
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
|
||||
pdfDoc = pdfDoc_;
|
||||
document.getElementById('page_count').textContent = pdfDoc.numPages;
|
||||
|
||||
// Initial/first page rendering
|
||||
renderPage(pageNum);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.pdf-page-container {
|
||||
margin: 0 auto;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.pdf-page {
|
||||
position: relative;
|
||||
margin: 0 auto 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.text-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0.2;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-layer span {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
cursor: text;
|
||||
transform-origin: 0% 0%;
|
||||
}
|
||||
|
||||
.text-layer ::selection {
|
||||
background: rgba(0,0,255,0.2);
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<<<<<<< HEAD
|
||||
<div x-data="{ isUploading: false }"
|
||||
<div x-data="{ isUploading: false,
|
||||
previewUrl: @entangle('imagePath').defer || ''}"
|
||||
x-on:livewire-upload-start="isUploading = true"
|
||||
x-on:livewire-upload-finish="isUploading = false"
|
||||
x-on:livewire-upload-error="isUploading = false">
|
||||
@@ -9,18 +9,25 @@
|
||||
|
||||
<!-- Zona de dropzone modificada -->
|
||||
<div wire:ignore
|
||||
x-on:drop.prevent="
|
||||
x-on:drop.prevent="
|
||||
const files = event.dataTransfer.files;
|
||||
if (files.length) {
|
||||
@this.upload('image', files[0]);
|
||||
updatePreview(files[0]);
|
||||
}
|
||||
$wire.toggleHover(false);
|
||||
event.stopPropagation(); // Evita propagación del evento
|
||||
"
|
||||
x-on:dragover.prevent="$wire.toggleHover(true)"
|
||||
x-on:dragleave.prevent="$wire.toggleHover(false)"
|
||||
class="dropzone {{ $hover ? 'dropzone-hover' : '' }} mb-3 p-4 border-2 border-dashed rounded text-center cursor-pointer">
|
||||
"
|
||||
x-on:dragover.prevent="$wire.toggleHover(true)"
|
||||
x-on:dragleave.prevent="$wire.toggleHover(false)"
|
||||
class="dropzone {{ $hover ? 'dropzone-hover' : '' }} mb-3 p-4 border-2 border-dashed rounded text-center cursor-pointer relative bg-cover bg-center"
|
||||
style="{{ $imagePath ? 'background-image: url(' . asset('storage/' . $imagePath) . ');' : '' }}"
|
||||
:style="previewUrl ? 'background-image: url(' + previewUrl + ')' : ''">
|
||||
|
||||
<!-- Overlay para mejor contraste -->
|
||||
<div class="absolute inset-0 bg-black/10 z-10"
|
||||
x-show="previewUrl || {{ $imagePath ? 'true' : 'false' }}"></div>
|
||||
|
||||
<!-- Contenido modificado -->
|
||||
<div wire:loading wire:target="image" class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
@@ -55,49 +62,4 @@
|
||||
<div class="alert alert-danger mt-2">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
=======
|
||||
<div>
|
||||
<!-- Preview de imagen -->
|
||||
<div class="relative mb-4">
|
||||
<img src="{{ $photo ? $photo->temporaryUrl() : ($currentImage ?? $placeholder) }}"
|
||||
alt="Preview"
|
||||
class="w-32 h-32 rounded-full object-cover border-2 border-gray-300">
|
||||
|
||||
@if($photo || $currentImage)
|
||||
<button type="button"
|
||||
wire:click="removePhoto"
|
||||
class="absolute top-0 right-0 bg-red-500 text-white rounded-full p-1 hover:bg-red-600 transition"
|
||||
title="Eliminar foto">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Input de archivo -->
|
||||
<label class="cursor-pointer bg-white px-4 py-2 rounded-md shadow-sm border border-gray-300 hover:bg-gray-50 inline-block">
|
||||
<span class="text-sm font-medium text-gray-700">
|
||||
{{ $photo || $currentImage ? 'Cambiar imagen' : 'Seleccionar imagen' }}
|
||||
</span>
|
||||
<input type="file"
|
||||
wire:model="photo"
|
||||
class="hidden"
|
||||
accept="image/*"
|
||||
name="{{ $fieldName }}_input"> <!-- Input oculto para Livewire -->
|
||||
|
||||
<!-- Input real para el formulario -->
|
||||
@if($photo)
|
||||
<input type="hidden" name="{{ $fieldName }}" value="{{ $photo->getFilename() }}">
|
||||
@elseif($currentImage)
|
||||
<input type="hidden" name="{{ $fieldName }}" value="current">
|
||||
@endif
|
||||
</label>
|
||||
|
||||
@error('photo')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<p class="mt-1 text-xs text-gray-500">PNG, JPG o JPEG (Max. 2MB)</p>
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
</div>
|
||||
74
resources/views/livewire/pdf-viewer.blade.php
Normal file
74
resources/views/livewire/pdf-viewer.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<div class="h-screen bg-gray-100" wire:ignore>
|
||||
<!-- Toolbar -->
|
||||
<div class="fixed top-0 left-0 right-0 bg-white shadow-lg p-4 flex gap-4 z-50">
|
||||
<button @click="zoomIn">➕</button>
|
||||
<button @click="zoomOut">➖</button>
|
||||
<input type="number" v-model="currentPage" @change="goToPage">
|
||||
<span>@{{ currentPage }}/@{{ totalPages }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Visor PDF -->
|
||||
<div id="pdf-container" class="pt-16">
|
||||
<livewire-pdf
|
||||
:url="$pdfUrl"
|
||||
@page-loaded="totalPages = $event.total"
|
||||
@page-changed="currentPage = $event.current"
|
||||
:page="$currentPage"
|
||||
:zoom="$zoomLevel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Canvas para Anotaciones -->
|
||||
<canvas id="annotation-layer" class="absolute top-0 left-0"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const fabricCanvas = new fabric.Canvas('annotation-layer');
|
||||
|
||||
// Lógica de interacción con Fabric.js
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
Livewire.on('refreshAnnotations', () => {
|
||||
// Cargar anotaciones desde backend
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('pdf-viewer');
|
||||
const pdfInstance = pdfjsLib.getDocument('{{ $pdfUrl }}');
|
||||
|
||||
pdfInstance.promise.then(pdf => {
|
||||
window.pdfDoc = pdf;
|
||||
Livewire.emit('totalPages', pdf.numPages);
|
||||
|
||||
// Función para renderizar página
|
||||
const renderPage = (num) => {
|
||||
pdf.getPage(num).then(page => {
|
||||
const viewport = page.getViewport({ scale: $wire.zoomLevel });
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild(canvas);
|
||||
|
||||
page.render({
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Escuchar cambios desde Livewire
|
||||
Livewire.on('pageChanged', page => renderPage(page));
|
||||
Livewire.on('zoomChanged', zoom => renderPage($wire.currentPage));
|
||||
|
||||
// Renderizar primera página
|
||||
renderPage($wire.currentPage);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -18,8 +18,11 @@
|
||||
<h1 class="mt-2 text-2xl font-bold">{{ $project->name }}</h1>
|
||||
</div>
|
||||
<a href="{{ route('projects.edit', $project) }}"
|
||||
class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
Editar Proyecto
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
Editar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,8 +49,8 @@
|
||||
<button
|
||||
wire:click="openUploadModal"
|
||||
class="flex items-center p-2 text-gray-600 hover:bg-gray-100 rounded">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z" />
|
||||
</svg>
|
||||
<span class="text-sm">Subir archivos</span>
|
||||
</button>
|
||||
@@ -67,6 +70,7 @@
|
||||
:currentFolder="$currentFolder"
|
||||
:expandedFolders="$expandedFolders"
|
||||
wire:key="folder-{{ $folder->id }}"
|
||||
:itemsCount="$this->documents->count()"
|
||||
/>
|
||||
@endforeach
|
||||
</ul>
|
||||
@@ -79,7 +83,7 @@
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50 sticky top-0">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Nombre</th>
|
||||
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Nombre - javi</th>
|
||||
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Versiones</th>
|
||||
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Última Actualización</th>
|
||||
<th class="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">Estado</th>
|
||||
@@ -96,6 +100,7 @@
|
||||
@php
|
||||
$type = App\Helpers\FileHelper::getFileType($document->name);
|
||||
$iconComponent = $iconComponent = "icons." . $type;
|
||||
|
||||
$iconClass = [
|
||||
'pdf' => 'pdf text-red-500',
|
||||
'word' => 'word text-blue-500',
|
||||
@@ -280,4 +285,4 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
@@ -75,19 +75,13 @@
|
||||
<!-- Foto del usuario -->
|
||||
@if($user->profile_photo_path)
|
||||
<div class="mr-3 flex-shrink-0">
|
||||
<<<<<<< HEAD
|
||||
<img src="{{ asset('storage/' . $user->profile_photo_path) }}"
|
||||
=======
|
||||
<img src="{{ asset($user->profile_photo_path) }}"
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
alt="{{ $user->full_name }}"
|
||||
class="w-8 h-8 rounded-full object-cover transition-transform group-hover:scale-110">
|
||||
</div>
|
||||
@else
|
||||
<div class="w-8 h-8 rounded-full bg-gray-200 mr-3 flex items-center justify-center transition-colors group-hover:bg-blue-100">
|
||||
<svg class="w-4 h-4 text-gray-500 group-hover:text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
<div class="w-8 h-8 rounded-full bg-gray-100 mr-3 flex items-center justify-center transition-colors group-hover:bg-blue-100">
|
||||
<flux:icon.user variant="solid" class="size-4" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -169,19 +163,17 @@
|
||||
<a href="{{ route('users.edit', $user) }}"
|
||||
class="text-blue-600 hover:text-blue-900"
|
||||
title="Editar usuario">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
<flux:icon.pencil class="size-5"/>
|
||||
</a>
|
||||
|
||||
<!-- Botón Eliminar -->
|
||||
<button wire:click="confirmDelete({{ $user->id }})"
|
||||
class="text-red-600 hover:text-red-900"
|
||||
title="Eliminar usuario">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
<flux:icon.trash class="size-5"/>
|
||||
</button>
|
||||
|
||||
<flux:button size="sm" icon="trash" variant="ghost" inset />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@stack('scripts')
|
||||
@fluxAppearance
|
||||
|
||||
@@ -1,417 +1,394 @@
|
||||
<!-- resources/views/projects/create.blade.php -->
|
||||
|
||||
<x-layouts.app :title="__('Create Project')">
|
||||
<div class="max-w-4xl mx-auto px-0 py-4">
|
||||
<header class="bg-white shadow">
|
||||
<div class="px-4 py-6 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<h2 class="flex items-center text-xl font-semibold leading-tight text-gray-800">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
||||
</svg>
|
||||
<x-layouts.app title="{{ (isset($project) && $project->id) ? __('Edit User') : __('Create User') }}"
|
||||
:showSidebar={{ $showSidebar }}>
|
||||
|
||||
{{ __('Nuevo Proyecto') }}
|
||||
|
||||
@error('error')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
</h2>
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<svg class="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<!-- Base abstract shape -->
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 4.5L20 8v8l-8 3.5L4 16V8l8-3.5z"/>
|
||||
|
||||
<!-- Progress indicator -->
|
||||
<path stroke-linecap="round"
|
||||
d="M12 6v12"
|
||||
class="text-blue-500"/>
|
||||
|
||||
<!-- Connection dots -->
|
||||
<circle cx="12" cy="6" r="1" fill="currentColor"/>
|
||||
<circle cx="12" cy="18" r="1" fill="currentColor"/>
|
||||
|
||||
<!-- Decorative elements -->
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M8 10l4 2 4-2"
|
||||
class="text-green-500"/>
|
||||
</svg>
|
||||
<h1 class="text-3xl font-bold text-gray-800">
|
||||
{{ (isset($project) && $project->id) ? 'Editar Proyecto' : 'Nuevo Proyecto' }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 text-sm">
|
||||
@if(isset($project) && $project->id)
|
||||
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
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
@if(session('error'))
|
||||
<div id="error-message" class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Formulario -->
|
||||
<form method="POST" action="{{ (isset($project) && $project->id) ? route('projects.update', $project) : route('projects.store') }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@if(isset($project) && $project->id)
|
||||
@method('PUT')
|
||||
@endif
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos Generales</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Referencia -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="reference" :value="__('Referencia')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<input type="text" name="reference"
|
||||
value="{{ old('reference', $project->reference ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
autofocus
|
||||
required>
|
||||
|
||||
@error('reference')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<form method="POST" action="{{ route('projects.store') }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<!-- Nombre -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="name" :value="__('Etiqueta')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<input type="text" name="name"
|
||||
value="{{ old('name', $project->name ?? '') }}"
|
||||
class="w-[500px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
autofocus
|
||||
required>
|
||||
|
||||
@error('name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Estado -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="status" :value="__('Estado del Proyecto')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<select id="status" name="status"
|
||||
class="w-[150px] block mt-1 border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<option :value="active" {{ old('status') == 'active' ? 'selected' : '' }}>Activo</option>
|
||||
<option :value="inactive" {{ old('status') == 'inactive' ? 'selected' : '' }}>Inactivo</option>
|
||||
</select>
|
||||
@error('status')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Descripción Rich Editor -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="description" :value="__('Descripción')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<!-- Editor Container -->
|
||||
<div id="rich-editor" style="height: 120px;">
|
||||
{!! old('description', $project->description ?? '') !!}
|
||||
</div>
|
||||
|
||||
<!-- Campo oculto para enviar el contenido -->
|
||||
<input type="hidden" id="description" name="description"
|
||||
value="{{ old('description', $project->description ?? '') }}">
|
||||
|
||||
@error('description')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos Generales</span>
|
||||
</div>
|
||||
<!-- Fechas Importantes -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="start_date" :value="__('Fechas')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-gray-700">de</span>
|
||||
<input type="date"
|
||||
id="start_date"
|
||||
name="start_date"
|
||||
value="{{ old('start_date') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<span class="text-gray-700">a</span>
|
||||
<input type="date"
|
||||
id="deadline"
|
||||
name="deadline"
|
||||
value="{{ old('end_date') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Ubicación </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Nombre -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="name" :value="__('Nombre del Proyecto')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<input type="text" name="name"
|
||||
value="{{ old('name', $project->name ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
autofocus
|
||||
required>
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Dirección -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Dirección')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-1">
|
||||
<div>
|
||||
<textarea id="address"
|
||||
name="address"
|
||||
rows="3"
|
||||
class="w-full border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">{{ old('address') }}
|
||||
|
||||
</textarea>
|
||||
|
||||
@error('address')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
@error('name')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Descripción -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="description" :value="__('Descripción')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<x-textarea id="description" class="block w-full mt-1" name="description" rows="4" required>
|
||||
{{ old('description') }}
|
||||
</x-textarea>
|
||||
@error('description')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Estado -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="status" :value="__('Estado del Proyecto')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<x-select id="status" name="status" class="block w-full mt-1">
|
||||
<option :value="active" {{ old('status') == 'active' ? 'selected' : '' }}>Activo</option>
|
||||
<option :value="inactive" {{ old('status') == 'inactive' ? 'selected' : '' }}>Inactivo</option>
|
||||
</x-select>
|
||||
@error('status')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Fechas Importantes -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="start_date" :value="__('Fechas')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="flex gap-4">
|
||||
<span class="text-gray-700">de</span>
|
||||
<input type="date"
|
||||
id="start_date"
|
||||
name="start_date"
|
||||
value="{{ old('start_date') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<span class="text-gray-700">a</span>
|
||||
<input type="date"
|
||||
id="deadline"
|
||||
name="deadline"
|
||||
value="{{ old('end_date') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<div>
|
||||
<input type="text"
|
||||
id="postal_code"
|
||||
name="postal_code"
|
||||
value="{{ old('postal_code') }}"
|
||||
placeholder="Código Postal"
|
||||
class="w-[120px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
@error('postal_code')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Ubicación </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Dirección -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Dirección')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-1">
|
||||
<div>
|
||||
<textarea id="address"
|
||||
name="address"
|
||||
rows="3"
|
||||
class="w-full border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">{{ old('address') }}
|
||||
|
||||
</textarea>
|
||||
|
||||
@error('address')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="text"
|
||||
id="postal_code"
|
||||
name="postal_code"
|
||||
value="{{ old('postal_code') }}"
|
||||
placeholder="Código Postal"
|
||||
class="w-[120px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
@error('postal_code')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="text"
|
||||
id="province"
|
||||
name="province"
|
||||
value="{{ old('province') }}"
|
||||
placeholder="Provincia"
|
||||
class="w-[300px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
@error('province')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<livewire:country-select :initialCountry="old('country')" />
|
||||
@error('country')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="text"
|
||||
id="province"
|
||||
name="province"
|
||||
value="{{ old('province') }}"
|
||||
placeholder="Provincia"
|
||||
class="w-[300px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
@error('province')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Mapa para Coordenadas -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Seleccione Ubicación')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div id="map" class="h-64 border-2 border-gray-200"></div>
|
||||
<div class="grid grid-cols-2 gap-4 mt-2">
|
||||
<div>
|
||||
<x-label for="latitude" :value="__('Latitud')" />
|
||||
<input type="number"
|
||||
id="latitude"
|
||||
name="latitude"
|
||||
value="{{ old('latitude') }}"
|
||||
step="any"
|
||||
class="border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<x-label for="longitude" :value="__('Longitud')" />
|
||||
<input type="number"
|
||||
<<<<<<< HEAD
|
||||
id="longitude"
|
||||
name="longitude"
|
||||
value="{{ old('longitude') }}"
|
||||
=======
|
||||
|
||||
<div>
|
||||
<livewire:country-select :initialCountry="old('country')" />
|
||||
@error('country')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Mapa para Coordenadas -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Seleccione Ubicación')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div id="map" class="h-80 border-2 border-gray-200"></div>
|
||||
<div class="grid grid-cols-2 gap-4 mt-2">
|
||||
<div>
|
||||
<x-label for="latitude" :value="__('Latitud')" />
|
||||
<input type="number"
|
||||
id="latitude"
|
||||
name="latitude"
|
||||
value="{{ old('latitude') }}"
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
step="any"
|
||||
class="border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</div>
|
||||
@error('latitude')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
@error('longitude')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<x-label for="longitude" :value="__('Longitud')" />
|
||||
<input type="number"
|
||||
id="longitude"
|
||||
name="longitude"
|
||||
value="{{ old('longitude') }}"
|
||||
step="any"
|
||||
class="border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</div>
|
||||
@error('latitude')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
@error('longitude')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Otros datos</span>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Otros datos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Imagen de Referencia -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="reference_image" :value="__('Imagen de Referencia')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="relative mt-1">
|
||||
<input type="file" id="project_image" name="project_image"
|
||||
class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-blue-50 file:text-blue-700
|
||||
hover:file:bg-blue-100"
|
||||
accept="image/*"
|
||||
onchange="previewImage(event)">
|
||||
<div class="mt-2" id="image-preview-container" style="display:none;">
|
||||
<img id="image-preview" class="object-cover h-48 w-full rounded-lg">
|
||||
</div>
|
||||
</div>
|
||||
@error('project_image')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Icono y Categorías -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Identificación Visual')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<x-label for="icon" :value="__('Icono del Proyecto')" />
|
||||
<div class="relative mt-1">
|
||||
<select id="icon" name="icon"
|
||||
class="w-full rounded-md shadow-sm border-gray-300 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50">
|
||||
<option :value="">Seleccionar Icono</option>
|
||||
@foreach(config('project.icons') as $icon)
|
||||
<option :value="{{ $icon }}"
|
||||
{{ old('icon') == $icon ? 'selected' : '' }}>
|
||||
<i class="fas fa-{{ $icon }} mr-2"></i>
|
||||
{{ Str::title($icon) }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-label for="categories" :value="__('Categorías')" />
|
||||
<x-multiselect
|
||||
name="categories[]"
|
||||
:options="$categories"
|
||||
:selected="old('categories', [])"
|
||||
placeholder="Seleccione categorías"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Archivos Adjuntos -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Documentos Iniciales')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div x-data="{ files: [] }" class="mt-1">
|
||||
<div class="flex items-center justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
||||
<div class="text-center">
|
||||
<input type="file"
|
||||
name="documents[]"
|
||||
multiple
|
||||
class="absolute opacity-0"
|
||||
x-ref="fileInput"
|
||||
@change="files = Array.from($event.target.files)">
|
||||
|
||||
<template x-if="files.length === 0">
|
||||
<div>
|
||||
<<<<<<< HEAD
|
||||
<x-icons icon="upload" class="mx-auto h-12 w-12 text-gray-400" />
|
||||
=======
|
||||
<x-icons.upload class="mx-auto h-12 w-12 text-gray-400" />
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
Arrastra archivos o haz clic para subir
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full mb-8">
|
||||
<tbody>
|
||||
<!-- Imagen de Referencia -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label for="reference_image" :value="__('Imagen de Referencia')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
@livewire('image-uploader', [
|
||||
'fieldName' => 'image_path', // Nombre del campo en la BD
|
||||
'label' => 'Imagen principal' // Etiqueta personalizada
|
||||
])
|
||||
<!-- Campo oculto para la ruta de la imagen -->
|
||||
<input type="hidden" name="project_image_path" id="PhotoPathInput"
|
||||
value="{{ old('project_image_path', optional($project)->project_image_path ?? '') }}">
|
||||
|
||||
<template x-if="files.length > 0">
|
||||
<div class="space-y-2">
|
||||
<template x-for="(file, index) in files" :key="index">
|
||||
<div class="flex items-center text-sm text-gray-600">
|
||||
<x-icons icon="document" class="w-5 h-5 mr-2" />
|
||||
<span x-text="file.name"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@error('project_image')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Categorías -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Identificación Visual')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div>
|
||||
<x-label for="categories" :value="__('Categorías')" />
|
||||
<x-multiselect
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
name="categories[]"
|
||||
:options="$categories"
|
||||
:selected="old('categories', [])"
|
||||
placeholder="Seleccione categorías"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Miembros del Equipo -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Miembros del Equipo')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="grid grid-cols-1 mt-2 gap-y-2 gap-x-4 sm:grid-cols-2">
|
||||
@foreach($users as $user)
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox"
|
||||
name="team[]"
|
||||
value="{{ $user->id }}"
|
||||
{{ in_array($user->id, old('team', [])) ? 'checked' : '' }}
|
||||
class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500">
|
||||
<span class="text-sm text-gray-700">{{ $user->first_name}} {{ $user->last_name}}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('team')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Botones de Acción -->
|
||||
<div class="flex justify-end mt-8 space-x-4">
|
||||
<a href="{{ route('projects.index') }}"
|
||||
class="px-4 py-2 text-gray-600 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50">
|
||||
{{ __('Cancelar') }}
|
||||
</a>
|
||||
<x-button type="submit" class="bg-blue-600 hover:bg-blue-700">
|
||||
<x-icons icon="save" class="w-5 h-5 mr-2" />
|
||||
{{ __('Crear Proyecto') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Miembros del Equipo -->
|
||||
<tr>
|
||||
<td class="w-1/4 py-3 pr-4 align-top">
|
||||
<x-label :value="__('Miembros del Equipo')" />
|
||||
</td>
|
||||
<td class="py-3">
|
||||
<div class="grid grid-cols-1 mt-2 gap-y-2 gap-x-4 sm:grid-cols-2">
|
||||
@foreach($users as $user)
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox"
|
||||
name="team[]"
|
||||
value="{{ $user->id }}"
|
||||
{{ in_array($user->id, old('team', [])) ? 'checked' : '' }}
|
||||
class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500">
|
||||
<span class="text-sm text-gray-700">{{ $user->first_name}} {{ $user->last_name}}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('team')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Botones de Acción -->
|
||||
<div class="flex justify-end mt-8 space-x-4">
|
||||
<a href="{{ route('projects.index') }}"
|
||||
class="px-4 py-2 text-gray-600 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50">
|
||||
{{ __('Cancelar') }}
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
|
||||
{{ (isset($project) && $project->id) ? 'Actualizar' : 'Crear' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@push('scripts')
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
|
||||
crossorigin=""/>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
|
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
|
||||
crossorigin=""></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.min.js"></script>
|
||||
@endpush
|
||||
|
||||
</div>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/@yaireo/tagify/dist/tagify.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
|
||||
|
||||
<!-- Leaflet CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
|
||||
crossorigin=""/>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@yaireo/tagify"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
|
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
|
||||
crossorigin=""></script>
|
||||
<script>
|
||||
// Editor Quill
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image', 'video', 'formula'],
|
||||
['link', 'formula'],
|
||||
|
||||
[{ 'header': 1 }, { 'header': 2 }], // custom button :values
|
||||
//[{ 'header': 1 }, { 'header': 2 }], // custom button :values
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||
[{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
|
||||
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
|
||||
[{ 'direction': 'rtl' }], // text direction
|
||||
|
||||
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
//[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||
|
||||
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
|
||||
@@ -425,72 +402,118 @@
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions,
|
||||
placeholder: 'Description...',
|
||||
}
|
||||
},
|
||||
placeholder: 'Escribe la descripción del proyecto...'
|
||||
});
|
||||
|
||||
// Tagify para categorías
|
||||
new Tagify(document.querySelector('[name="categories[]"]'), {
|
||||
enforceWhitelist: true,
|
||||
whitelist: @json($categories->pluck('name')),
|
||||
dropdown: {
|
||||
enabled: 1,
|
||||
maxItems: 5
|
||||
}
|
||||
// Actualizar el input hidden con el contenido HTML
|
||||
quill.on('text-change', function() {
|
||||
document.getElementById('description').value = quill.root.innerHTML;
|
||||
});
|
||||
|
||||
// Inicializar con contenido existente si hay
|
||||
quill.clipboard.dangerouslyPasteHTML(
|
||||
document.getElementById('description').value
|
||||
);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function previewImage(event) {
|
||||
const reader = new FileReader();
|
||||
const preview = document.getElementById('image-preview');
|
||||
|
||||
reader.onload = function() {
|
||||
preview.innerHTML = `
|
||||
<img src="${reader.result}"
|
||||
class="object-cover w-full h-48 rounded-lg shadow-sm"
|
||||
alt="Vista previa de la imagen">
|
||||
`;
|
||||
}
|
||||
|
||||
if(event.target.files[0]) {
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
}
|
||||
// Escuchar el evento de Livewire y actualizar el campo oculto
|
||||
document.addEventListener('imageUploaded', (event) => {
|
||||
document.getElementById('PhotoPathInput').value = event.detail.path;
|
||||
});
|
||||
|
||||
document.addEventListener('imageRemoved', (event) => {
|
||||
document.getElementById('PhotoPathInput').value = '';
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
let map;
|
||||
let marker;
|
||||
|
||||
|
||||
function initMap() {
|
||||
// Coordenadas iniciales (usar valores por defecto o geolocalización)
|
||||
const defaultLat = {{ old('latitude', 40.4168) }};
|
||||
const defaultLng = {{ old('longitude', -3.7038) }};
|
||||
// Obtener valores iniciales de los inputs o usar defaults
|
||||
const initialLat = parseFloat(document.getElementById('latitude').value) || 40.4168;
|
||||
const initialLng = parseFloat(document.getElementById('longitude').value) || -3.7038;
|
||||
|
||||
map = L.map('map').setView([defaultLat, defaultLng], 13);
|
||||
map = L.map('map').setView([initialLat, initialLng], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||
|
||||
// Marcador inicial si hay valores
|
||||
if(defaultLat && defaultLng) {
|
||||
marker = L.marker([defaultLat, defaultLng]).addTo(map);
|
||||
// Marcador inicial si hay valores válidos
|
||||
if (!isNaN(initialLat) && !isNaN(initialLng)) {
|
||||
marker = L.marker([initialLat, initialLng]).addTo(map);
|
||||
}
|
||||
|
||||
// Manejar clic en el mapa
|
||||
// Actualizar inputs al hacer clic en el mapa
|
||||
map.on('click', function(e) {
|
||||
if(marker) map.removeLayer(marker);
|
||||
marker = L.marker(e.latlng).addTo(map);
|
||||
document.getElementById('latitude').:value = e.latlng.lat.toFixed(6);
|
||||
document.getElementById('longitude').:value = e.latlng.lng.toFixed(6);
|
||||
updateInputs(e.latlng.lat, e.latlng.lng);
|
||||
updateMarker(e.latlng);
|
||||
});
|
||||
}
|
||||
|
||||
// Inicializar después de cargar Leaflet
|
||||
function updateMarker(latlng) {
|
||||
if (marker) {
|
||||
marker.setLatLng(latlng);
|
||||
} else {
|
||||
marker = L.marker(latlng).addTo(map);
|
||||
}
|
||||
map.panTo(latlng);
|
||||
}
|
||||
|
||||
function updateInputs(lat, lng) {
|
||||
document.getElementById('latitude').value = lat.toFixed(6);
|
||||
document.getElementById('longitude').value = lng.toFixed(6);
|
||||
}
|
||||
|
||||
function updateMapFromInputs() {
|
||||
const lat = parseFloat(document.getElementById('latitude').value);
|
||||
const lng = parseFloat(document.getElementById('longitude').value);
|
||||
|
||||
if (!isNaN(lat) && !isNaN(lng) &&
|
||||
lat >= -90 && lat <= 90 &&
|
||||
lng >= -180 && lng <= 180) {
|
||||
|
||||
const newLatLng = L.latLng(lat, lng);
|
||||
updateMarker(newLatLng);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar el mapa
|
||||
window.onload = function() {
|
||||
initMap();
|
||||
|
||||
// Escuchar cambios en los inputs
|
||||
document.getElementById('latitude').addEventListener('input', updateMapFromInputs);
|
||||
document.getElementById('longitude').addEventListener('input', updateMapFromInputs);
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<!-- Sección de Proyectos -->
|
||||
<flux:navlist.group :heading="__('Projects')">
|
||||
<flux:navlist.item
|
||||
icon="folder"
|
||||
:href="route('projects.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Projects') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="plus"
|
||||
:href="route('projects.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create Project') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
</x-layouts.app>
|
||||
@@ -1,115 +1,158 @@
|
||||
<!-- resources/views/projects/index.blade.php -->
|
||||
<x-layouts.app :title="__('Proyectos')">
|
||||
<x-slot name="header">
|
||||
<h2 class="text-xl font-semibold leading-tight text-gray-800">
|
||||
{{ __('Mis Proyectos') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-6">
|
||||
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||
<!-- Barra de búsqueda y creación -->
|
||||
<div class="flex flex-col justify-between mb-6 space-y-4 md:flex-row md:space-y-0">
|
||||
<div class="flex-1 max-w-md">
|
||||
<form action="{{ route('projects.index') }}" method="GET">
|
||||
<div class="relative">
|
||||
<input type="search" name="search" value="{{ request('search') }}"
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Buscar proyectos...">
|
||||
<x-icons icon="search" class="absolute left-3 top-2.5 w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
</form>
|
||||
<x-layouts.app :title="__('Proyectos')" :showSidebar={{ $showSidebar }}>
|
||||
<!-- Barra de búsqueda y creación -->
|
||||
<div class="flex flex-col justify-between mb-6 space-y-4 md:flex-row md:space-y-0">
|
||||
<div class="flex-1 max-w-md">
|
||||
<form action="{{ route('projects.index') }}" method="GET">
|
||||
<div class="relative">
|
||||
<input type="search" name="search" value="{{ request('search') }}"
|
||||
class="w-full pl-10 pr-4 py-2 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Buscar proyectos...">
|
||||
<x-icons icon="search" class="absolute left-3 top-2.5 w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
|
||||
@can('create', App\Models\Project::class)
|
||||
<a href="{{ route('projects.create') }}"
|
||||
class="flex items-center px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
<x-icons icon="plus" class="w-5 h-5 mr-2" />
|
||||
{{ __('Nuevo Proyecto') }}
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@can('create', App\Models\Project::class)
|
||||
<a href="{{ route('projects.create') }}"
|
||||
class="flex items-center px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
<x-icons icon="plus" class="w-5 h-5 mr-2" />
|
||||
{{ __('Nuevo') }}
|
||||
</a>
|
||||
@endcan
|
||||
</div>
|
||||
|
||||
<!-- Listado de proyectos -->
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse($projects as $project)
|
||||
<div class="overflow-hidden bg-white rounded-lg shadow">
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<a href="{{ route('projects.show', $project) }}"
|
||||
class="hover:text-blue-600 hover:underline">
|
||||
{{ $project->name }}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
{{ Str::limit($project->description, 100) }}
|
||||
</p>
|
||||
</div>
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="p-1 text-gray-400 hover:text-gray-600">
|
||||
<x-icons icon="dots-vertical class="w-5 h-5" />
|
||||
</button>
|
||||
</x-slot>
|
||||
<!-- Listado de proyectos -->
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse($projects as $project)
|
||||
<div class="overflow-hidden bg-white rounded-lg shadow">
|
||||
<!-- Imagen del proyecto -->
|
||||
@if($project->project_image_path)
|
||||
<img src="{{ asset('storage/' . $project->project_image_path) }}"
|
||||
alt="Imagen del proyecto {{ $project->name }}"
|
||||
class="object-cover w-full h-48 border-b">
|
||||
@endif
|
||||
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<a href="{{ route('projects.show', $project) }}"
|
||||
class="hover:text-blue-600 hover:underline">
|
||||
{{ $project->name }}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
{{ Str::limit($project->description, 100) }}
|
||||
</p>
|
||||
</div>
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="p-1 text-gray-400 hover:text-gray-600">
|
||||
<x-icons icon="dots-vertical class="w-5 h-5" />
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link href="{{ route('projects.show', $project) }}">
|
||||
<x-icons icon="eye" class="w-5 h-5 mr-2" /> Ver Detalles
|
||||
</x-dropdown-link>
|
||||
|
||||
@can('update', $project)
|
||||
<x-dropdown-link href="{{ route('projects.edit', $project) }}">
|
||||
<x-icons icon="pencil" class="w-5 h-5 mr-2" /> Editar
|
||||
</x-dropdown-link>
|
||||
@endcan
|
||||
|
||||
@can('delete', $project)
|
||||
<form method="POST" action="{{ route('projects.destroy', $project) }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-dropdown-link href="#"
|
||||
onclick="event.preventDefault(); this.closest('form').submit();"
|
||||
class="text-red-600 hover:bg-red-50">
|
||||
<x-icons icon="trash" class="w-5 h-5 mr-2" /> Eliminar
|
||||
</x-dropdown-link>
|
||||
</form>
|
||||
@endcan
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Metadatos del proyecto -->
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center justify-between mt-3 text-sm">
|
||||
<span class="flex items-center text-gray-500">
|
||||
<x-icons icon="document" class="w-4 h-4 mr-1" />
|
||||
{{ $project->documents_count }} documentos
|
||||
</span>
|
||||
<span class="px-2 py-1 text-sm rounded-full
|
||||
{{ $project->status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
|
||||
{{ __(Str::ucfirst($project->status)) }}
|
||||
</span>
|
||||
</div>
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link href="{{ route('projects.show', $project) }}">
|
||||
<x-icons icon="eye" class="w-5 h-5 mr-2" /> Ver Detalles
|
||||
</x-dropdown-link>
|
||||
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<x-icons icon="calendar" class="w-4 h-4 mr-1 inline" />
|
||||
Creado {{ $project->created_at->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
@can('update', $project)
|
||||
<x-dropdown-link href="{{ route('projects.edit', $project) }}">
|
||||
<x-icons icon="pencil" class="w-5 h-5 mr-2" /> Editar
|
||||
</x-dropdown-link>
|
||||
@endcan
|
||||
|
||||
@can('delete', $project)
|
||||
<form method="POST" action="{{ route('projects.destroy', $project) }}">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-dropdown-link href="#"
|
||||
onclick="event.preventDefault(); this.closest('form').submit();"
|
||||
class="text-red-600 hover:bg-red-50">
|
||||
<x-icons icon="trash" class="w-5 h-5 mr-2" /> Eliminar
|
||||
</x-dropdown-link>
|
||||
</form>
|
||||
@endcan
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Metadatos del proyecto -->
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center justify-between mt-3 text-sm">
|
||||
<span class="flex items-center text-gray-500">
|
||||
<x-icons icon="document" class="w-4 h-4 mr-1" />
|
||||
{{ $project->documents_count }} documentos
|
||||
</span>
|
||||
<span class="px-2 py-1 text-sm rounded-full
|
||||
{{ $project->status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
|
||||
{{ __(Str::ucfirst($project->status)) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<x-icons icon="calendar" class="w-4 h-4 mr-1 inline" />
|
||||
Creado {{ $project->created_at->diffForHumans() }}
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="p-6 text-center text-gray-500 col-span-full">
|
||||
<x-icons icon="folder-remove" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||
{{ __('No tienes proyectos asignados.') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
|
||||
</div>
|
||||
@empty
|
||||
<div class="p-6 text-center text-gray-500 col-span-full">
|
||||
<x-icons icon="folder-remove" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||
{{ __('No tienes proyectos asignados.') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<!-- Sección de Usuarios -->
|
||||
<flux:navlist.group :heading="__('User')">
|
||||
<flux:navlist.item
|
||||
icon="users"
|
||||
:href="route('users.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Users') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="user-plus"
|
||||
:href="route('users.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create User') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:separator />
|
||||
|
||||
<!-- Sección de Proyectos -->
|
||||
<flux:navlist.group :heading="__('Projects')">
|
||||
<flux:navlist.item
|
||||
icon="folder"
|
||||
:href="route('projects.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Projects') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="plus"
|
||||
:href="route('projects.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create Project') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
</x-layouts.app>
|
||||
@@ -1,33 +0,0 @@
|
||||
<x-layouts.app :title="__('Show Project')">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
||||
<!-- Barra de herramientas con breadcrumbs -->
|
||||
<div class="mb-6">
|
||||
<nav class="flex" aria-label="Breadcrumb">
|
||||
<ol class="flex items-center space-x-2">
|
||||
<li>
|
||||
<div class="flex items-center">
|
||||
<a href="{{ route('projects.index') }}" class="text-gray-400 hover:text-gray-500">
|
||||
<x-icons icon="home" class="h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Barra de herramientas principal -->
|
||||
<<<<<<< HEAD
|
||||
javi
|
||||
=======
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
|
||||
|
||||
<!-- Componente principal Livewire -->
|
||||
|
||||
|
||||
<!-- Modales -->
|
||||
|
||||
</div>
|
||||
|
||||
</x-layouts.app>
|
||||
@@ -1,332 +1,312 @@
|
||||
<x-layouts.app title="{{ isset($user) ? __('Edit User') : __('Create User') }}">
|
||||
<div class="max-w-4xl mx-auto px-0 py-4">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<svg class="w-10 h-10 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
<h1 class="text-3xl font-bold text-gray-800">
|
||||
{{ isset($user) ? 'Editar Usuario' : 'Nuevo Usuario' }}
|
||||
</h1>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm">
|
||||
@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
|
||||
</p>
|
||||
|
||||
<x-layouts.app title="{{ (isset($user) && $user->id) ? __('Edit User') : __('Create User') }}" :showSidebar={{ $showSidebar }}>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<flux:icon.user class="size-10" variant="solid"/>
|
||||
<h1 class="text-3xl font-bold text-gray-800">
|
||||
{{ (isset($user) && $user->id) ? 'Editar Usuario' : 'Nuevo Usuario' }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@if(session('error'))
|
||||
<div id="error-message" class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($errors->any())
|
||||
<div class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
<ul>
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ isset($user) ? route('users.update', $user) : route('users.store') }}">
|
||||
@csrf
|
||||
@isset($user)
|
||||
@method('PUT')
|
||||
<p class="text-gray-600 text-sm">
|
||||
@if(isset($user) && $user->id)
|
||||
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
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos Personales</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos Personales -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody class="space-y-4">
|
||||
<!-- Título de cortesía -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Título de cortesía
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<select name="title" class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="Sr.">Sr.</option>
|
||||
<option value="Sra.">Sra.</option>
|
||||
<option value="Dr.">Dr.</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nombre -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Nombre
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="first_name" required
|
||||
value="{{ old('first_name', $user->first_name ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
autofocus>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Apellidos -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Apellidos
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="last_name" required
|
||||
value="{{ old('last_name', $user->last_name ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nombre de Login -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Nombre de Login
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="username" required
|
||||
value="{{ old('username', $user->username ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Configuración de acceso</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración de Acceso -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<!-- Intervalo de fechas -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Validez de acceso
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex gap-4">
|
||||
de
|
||||
<input type="date" name="start_date"
|
||||
value="{{ old('start_date', isset($user->access_start) ? $user->access_start->format('Y-m-d') : '') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
a
|
||||
<input type="date" name="end_date"
|
||||
value="{{ old('end_date', isset($user->access_end) ? $user->access_end->format('Y-m-d') : '') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Contraseña -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Contraseña
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex gap-2">
|
||||
<input type="text" name="password" id="password"
|
||||
{{ !isset($user) ? 'required' : '' }}
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<button type="button" onclick="generatePassword()"
|
||||
class="px-2 py-1 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos de contacto</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos de Contacto -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Email
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<input type="email" name="email" required
|
||||
value="{{ old('email', $user->email ?? '') }}"
|
||||
class="w-[300px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Teléfono
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"/>
|
||||
</svg>
|
||||
<input type="tel" name="phone"
|
||||
value="{{ old('phone', $user->phone ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="py-2 pr-4 align-top">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Dirección
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400 mt-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
<textarea name="address" rows="3"
|
||||
class="w-full border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">{{ old('address', $user->address ?? '') }}</textarea>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Otros datos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos de Contacto -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<!-- Estado -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Estado
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<select name="is_active" class="w-[200px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<option value="Activo">Activo</option>
|
||||
<option value="Inactivo">Inactivo</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Foto -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Foto
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="mb-6">
|
||||
<<<<<<< HEAD
|
||||
@livewire('image-uploader', [
|
||||
'fieldName' => 'image_path', // Nombre del campo en la BD
|
||||
'label' => 'Imagen principal' // Etiqueta personalizada
|
||||
])
|
||||
<!-- Campo oculto para la ruta de la imagen -->
|
||||
<input type="hidden" name="profile_photo_path" id="profilePhotoPathInput" value="{{ old('profile_photo_path', $user->profile_photo_path) }}">
|
||||
=======
|
||||
|
||||
@livewire('image-uploader', [
|
||||
'fieldName' => 'profile_photo_path',
|
||||
'currentImage' => $user->profile_photo_path ?? null,
|
||||
'placeholder' => asset('images/default-user.png')
|
||||
])
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
</div>
|
||||
</td>
|
||||
</tr>php
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Botón de envío -->
|
||||
<div class="text-right">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
|
||||
{{ isset($user) ? 'Actualizar Usuario' : 'Crear Usuario' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@if(session('error'))
|
||||
<div id="error-message" class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($errors->any())
|
||||
<div class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
<ul>
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ (isset($user) && $user->id) ? route('users.update', $user) : route('users.store') }}">
|
||||
@csrf
|
||||
@isset($user)
|
||||
@method('PUT')
|
||||
@endisset
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos Personales</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos Personales -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody class="space-y-4">
|
||||
<!-- Título de cortesía -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Título de cortesía
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<select name="title" class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="Sr.">Sr.</option>
|
||||
<option value="Sra.">Sra.</option>
|
||||
<option value="Dr.">Dr.</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nombre -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Nombre
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="first_name" required
|
||||
value="{{ old('first_name', $user->first_name ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
|
||||
autofocus>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Apellidos -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Apellidos
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="last_name" required
|
||||
value="{{ old('last_name', $user->last_name ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nombre de Login -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Nombre de Login
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<input type="text" name="username" required
|
||||
value="{{ old('username', $user->username ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Configuración de acceso</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuración de Acceso -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<!-- Intervalo de fechas -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Validez de acceso
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex gap-4">
|
||||
de
|
||||
<input type="date" name="start_date"
|
||||
value="{{ old('start_date', isset($user->access_start) ? $user->access_start->format('Y-m-d') : '') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
a
|
||||
<input type="date" name="end_date"
|
||||
value="{{ old('end_date', isset($user->access_end) ? $user->access_end->format('Y-m-d') : '') }}"
|
||||
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Contraseña -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Contraseña
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex gap-2">
|
||||
<input type="text" name="password" id="password"
|
||||
{{ !isset($user) ? 'required' : '' }}
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<button type="button" onclick="generatePassword()"
|
||||
class="px-2 py-1 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Datos de contacto</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos de Contacto -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Email
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon.envelope class="w-5 h-5"/>
|
||||
<input type="email" name="email" required
|
||||
value="{{ old('email', $user->email ?? '') }}"
|
||||
class="w-[300px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Teléfono
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon.phone class="w-5 h-5"/>
|
||||
<input type="tel" name="phone"
|
||||
value="{{ old('phone', $user->phone ?? '') }}"
|
||||
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="py-2 pr-4 align-top">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Dirección
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<flux:icon.map-pin class="w-5 h-5"/>
|
||||
<textarea name="address" rows="3"
|
||||
class="w-full border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">{{ old('address', $user->address ?? '') }}</textarea>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Separador -->
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-4 bg-white text-sm text-gray-500">Otros datos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datos de Contacto -->
|
||||
<div class="bg-white py-6">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<!-- Estado -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4 w-1/3">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Estado
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<select name="is_active" class="w-[200px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
|
||||
<option value="Activo">Activo</option>
|
||||
<option value="Inactivo">Inactivo</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Foto -->
|
||||
<tr>
|
||||
<td class="py-2 pr-4">
|
||||
<label class="block text-sm font-bold text-gray-700">
|
||||
Foto
|
||||
</label>
|
||||
</td>
|
||||
<td class="py-2">
|
||||
<div class="mb-6">
|
||||
@livewire('image-uploader', [
|
||||
'fieldName' => 'image_path', // Nombre del campo en la BD
|
||||
'label' => 'Imagen principal' // Etiqueta personalizada
|
||||
])
|
||||
<!-- Campo oculto para la ruta de la imagen -->
|
||||
<input type="hidden" name="profile_photo_path" id="profilePhotoPathInput" value="{{ old('profile_photo_path', optional($user)->profile_photo_path ?? '') }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>php
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Botón de envío -->
|
||||
<div class="text-right">
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
|
||||
{{ (isset($user) && $user->id) ? 'Actualizar Usuario' : 'Crear Usuario' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
<<<<<<< HEAD
|
||||
// Escuchar el evento de Livewire y actualizar el campo oculto
|
||||
document.addEventListener('imageUploaded', (event) => {
|
||||
document.getElementById('profilePhotoPathInput').value = event.detail.path;
|
||||
@@ -336,8 +316,6 @@
|
||||
document.getElementById('profilePhotoPathInput').value = '';
|
||||
});
|
||||
|
||||
=======
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
function generatePassword() {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
|
||||
let password = '';
|
||||
@@ -360,4 +338,31 @@
|
||||
*/
|
||||
</script>
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<!-- Sección de Usuarios -->
|
||||
<flux:navlist.group :heading="__('User')">
|
||||
<flux:navlist.item
|
||||
icon="users"
|
||||
:href="route('users.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Users') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="user-plus"
|
||||
:href="route('users.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create User') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:separator />
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
</x-layouts.app>
|
||||
@@ -1,17 +1,42 @@
|
||||
<x-layouts.app :title="__('Users')">
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<svg class="w-8 h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Usuarios</h1>
|
||||
</div>
|
||||
<a href="{{ route('users.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
Nuevo
|
||||
</a>
|
||||
<x-layouts.app :title="__('Users')" :showSidebar={{ $showSidebar }}>
|
||||
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<flux:icon.user class="size-8" variant="solid" />
|
||||
<h1 class="text-3xl font-bold text-gray-800">Usuarios</h1>
|
||||
</div>
|
||||
|
||||
@livewire('user-table')
|
||||
<a href="{{ route('users.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
Nuevo
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@livewire('user-table')
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<!-- Sección de Usuarios -->
|
||||
<flux:navlist.group :heading="__('User')">
|
||||
<flux:navlist.item
|
||||
icon="users"
|
||||
:href="route('users.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Users') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="user-plus"
|
||||
:href="route('users.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create User') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:separator />
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
</x-layouts.app>
|
||||
@@ -1,295 +1,249 @@
|
||||
<x-layouts.app :title="__('Show User')">
|
||||
|
||||
<div class="container mx-auto px-4 py-6">
|
||||
<!-- Header Section -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 flex items-center justify-between">
|
||||
<!-- User Info Left -->
|
||||
<div class="flex items-center space-x-6">
|
||||
<!-- User Photo -->
|
||||
<<<<<<< HEAD
|
||||
<img src="{{ $user->profile_photo_path ? asset('storage/' . $user->profile_photo_path) : 'https://via.placeholder.com/150' }}"
|
||||
class="w-24 h-24 rounded-full object-cover border-2 border-blue-100">
|
||||
=======
|
||||
<img src="{{ $user->photo_url ?? 'https://via.placeholder.com/150' }}"
|
||||
class="w-24 h-24 rounded-full object-cover border-4 border-blue-100">
|
||||
<x-layouts.app :title="__('Show User')" :showSidebar={{ $showSidebar }}>
|
||||
|
||||
<!-- Header Section -->
|
||||
<div class="bg-white p-6 mb-6 flex items-center justify-between">
|
||||
<!-- User Info Left -->
|
||||
<div class="flex items-center space-x-6">
|
||||
<!-- User Photo -->
|
||||
<flux:avatar
|
||||
class="w-24 h-24 rounded-lg shadow-lg object-cover border-2 border-gray-600"
|
||||
namue="{{ $user->first_name }} {{ $user->last_name }}"
|
||||
src="{{ $user->profile_photo_path ? asset('storage/' . $user->profile_photo_path) : null }}" />
|
||||
|
||||
<!-- User Details -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-700">
|
||||
{{ $user->first_name }} {{ $user->last_name }}
|
||||
</h1>
|
||||
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<!-- User Details -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-700">
|
||||
{{ $user->first_name }} {{ $user->last_name }}
|
||||
</h1>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="mt-2 space-y-1">
|
||||
<!-- Contact Info -->
|
||||
<div class="mt-2 space-y-1">
|
||||
@if($user->address )
|
||||
<div class="flex items-center text-gray-600">
|
||||
<flux:icon.map-pin />
|
||||
<p class="text-sm text-gray-700">
|
||||
{{ $user->first_name }}
|
||||
{{ $user->address }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center text-gray-600">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8l8 5 8-5v10zm-8-7L4 6h16l-8 5z"/>
|
||||
</svg>
|
||||
<a href="mailto:{{ $user->email }}" class="hover:text-blue-600">
|
||||
{{ $user->email }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if($user->phone)
|
||||
<div class="flex items-center text-gray-600">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/>
|
||||
</svg>
|
||||
<a href="tel:{{ $user->phone }}" class="hover:text-blue-600">
|
||||
{{ $user->phone }}
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Section -->
|
||||
<div class="flex flex-col items-end space-y-4">
|
||||
<!-- Navigation Toolbar -->
|
||||
<div class="flex space-x-2">
|
||||
<a href="{{ route('users.index') }}"
|
||||
class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($previousUser)
|
||||
<a href="{{ route('users.show', $previousUser) }}" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
<div class="flex items-center text-gray-600">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8l8 5 8-5v10zm-8-7L4 6h16l-8 5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($nextUser)
|
||||
<a href="{{ route('users.show', $nextUser) }}"
|
||||
class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
<a href="mailto:{{ $user->email }}" class="hover:text-blue-600">
|
||||
{{ $user->email }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if($user->phone)
|
||||
<div class="flex items-center text-gray-600">
|
||||
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="tel:{{ $user->phone }}" class="hover:text-blue-600">
|
||||
{{ $user->phone }}
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Status Badge -->
|
||||
<span class="px-4 py-2 w-30 rounded-lg text-sm text-center font-semibold
|
||||
{{ $user->is_active ? 'bg-green-200 text-green-800' : 'bg-red-100 text-red-800' }}">
|
||||
{{ $user->is_active ? 'Activo' : 'Inactivo' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div x-data="{ activeTab: 'info' }" class="bg-white rounded-lg shadow-md">
|
||||
<!-- Tab Headers -->
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="flex space-x-8 px-6">
|
||||
<button @click="activeTab = 'info'"
|
||||
:class="activeTab === 'info' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Información del Usuario
|
||||
</button>
|
||||
|
||||
<button @click="activeTab = 'permissions'"
|
||||
:class="activeTab === 'permissions' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Permisos
|
||||
</button>
|
||||
<!-- Right Section -->
|
||||
<div class="flex flex-col items-end space-y-4">
|
||||
<!-- Navigation Toolbar -->
|
||||
<div class="flex space-x-2">
|
||||
<a href="{{ route('users.index') }}"
|
||||
class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
@if($previousUser)
|
||||
<a href="{{ route('users.show', $previousUser) }}" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<button @click="activeTab = 'projects'"
|
||||
:class="activeTab === 'projects' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Proyectos
|
||||
</button>
|
||||
</nav>
|
||||
@if($nextUser)
|
||||
<a href="{{ route('users.show', $nextUser) }}"
|
||||
class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg flex items-center">
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="p-6">
|
||||
<!-- Info Tab -->
|
||||
<div x-show="activeTab === 'info'">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach(['name' => 'Nombre', 'last_name' => 'Apellido', 'email' => 'Email', 'phone' => 'Teléfono', 'created_at' => 'Fecha Registro'] as $field => $label)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $label }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $user->$field ?? 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Status Badge -->
|
||||
<span class="px-4 py-2 w-30 rounded-lg text-sm text-center font-semibold
|
||||
{{ $user->is_active ? 'bg-green-600 text-white' : 'bg-red-100 text-red-800' }}">
|
||||
{{ $user->is_active ? 'Activo' : 'Inactivo' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<<<<<<< HEAD
|
||||
<div class="mt-6 flex justify-end space-x-4">
|
||||
<a href="{{ route('users.edit', $user) }}"
|
||||
class="w-[150px] px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center justify-center">
|
||||
=======
|
||||
<div class="mt-6 flex space-x-4">
|
||||
<a href="{{ route('users.edit', $user) }}"
|
||||
class="px-4 py-2 w-[150px] bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center">
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<!-- Contend: -->
|
||||
<div x-data="{ activeTab: 'info' }" class="bg-white rounded-lg shadow-md border-1">
|
||||
<!-- Tab Headers -->
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="flex space-x-8 px-6">
|
||||
<button @click="activeTab = 'info'"
|
||||
:class="activeTab === 'info' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Usuario
|
||||
</button>
|
||||
|
||||
<button @click="activeTab = 'permissions'"
|
||||
:class="activeTab === 'permissions' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Permisos
|
||||
</button>
|
||||
|
||||
<button @click="activeTab = 'projects'"
|
||||
:class="activeTab === 'projects' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'"
|
||||
class="py-4 px-1 border-b-2 font-medium">
|
||||
Proyectos
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="p-6">
|
||||
<!-- Info Tab -->
|
||||
<div x-show="activeTab === 'info'">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach(['name' => 'Nombre', 'last_name' => 'Apellido', 'email' => 'Email', 'phone' => 'Teléfono', 'created_at' => 'Fecha Registro'] as $field => $label)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $label }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $user->$field ?? 'N/A' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-6 flex justify-end space-x-4">
|
||||
<a href="{{ route('users.edit', $user) }}"
|
||||
class="w-[150px] px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
Editar
|
||||
</a>
|
||||
|
||||
{{-- Formulario de Edición --}}
|
||||
<form method="POST" action="{{ route('users.update', $user) }}">
|
||||
@csrf
|
||||
@method('PUT') <!-- Important! -->
|
||||
<button type="submit"
|
||||
class="w-[150px] px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white rounded-lg flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
|
||||
</svg>
|
||||
Editar
|
||||
</a>
|
||||
{{ $user->is_active ? 'Desactivar' : 'Activar' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Formulario de Edición --}}
|
||||
<form method="POST" action="{{ route('users.update', $user) }}">
|
||||
@csrf
|
||||
@method('PUT') <!-- Important! -->
|
||||
<button type="submit"
|
||||
<<<<<<< HEAD
|
||||
class="w-[150px] px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white rounded-lg flex items-center justify-center">
|
||||
=======
|
||||
class="px-4 py-2 w-[150px] {{ $user->is_active ? 'bg-red-600 hover:bg-red-700' : 'bg-green-600 hover:bg-green-700' }} text-white rounded-lg flex items-center">
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
|
||||
</svg>
|
||||
{{ $user->is_active ? 'Desactivar' : 'Activar' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{{-- Formulario de Eliminación --}}
|
||||
<form method="POST" action="{{ route('users.destroy', $user) }}">
|
||||
@csrf
|
||||
@method('DELETE') <!-- Important! -->
|
||||
<button type="submit"
|
||||
onclick="return confirm('¿Estás seguro de querer eliminar este usuario?')"
|
||||
<<<<<<< HEAD
|
||||
class="px-4 py-2 w-[150px] bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center justify-center">
|
||||
=======
|
||||
class="px-4 py-2 w-[150px] bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center">
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
Eliminar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{-- Formulario de Eliminación --}}
|
||||
<form method="POST" action="{{ route('users.destroy', $user) }}">
|
||||
@csrf
|
||||
@method('DELETE') <!-- Important! -->
|
||||
<button type="submit"
|
||||
onclick="return confirm('¿Estás seguro de querer eliminar este usuario?')"
|
||||
class="px-4 py-2 w-[150px] bg-red-600 hover:bg-red-700 text-white rounded-lg flex items-center justify-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
Eliminar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permissions Tab -->
|
||||
<<<<<<< HEAD
|
||||
<div class="space-y-4">
|
||||
@foreach($permissionGroups as $groupName => $group)
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<!-- Group Header -->
|
||||
<div class="bg-gray-50 px-4 py-3 flex justify-between items-center border-b">
|
||||
<h3 class="font-semibold text-gray-700">
|
||||
{{ ucfirst($groupName) }} Permissions
|
||||
</h3>
|
||||
<!-- Permissions Tab -->
|
||||
<div x-show="activeTab === 'permissions'" x-cloak>
|
||||
<div class="space-y-4">
|
||||
@foreach($permissionGroups as $groupName => $group)
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<!-- Group Header -->
|
||||
<div class="bg-gray-50 px-4 py-3 flex justify-between items-center border-b">
|
||||
<h3 class="font-semibold text-gray-700">
|
||||
{{ ucfirst($groupName) }}
|
||||
</h3>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<!-- Toggle All On -->
|
||||
<button wire:click="toggleAllGroupPermissions('{{ $groupName }}', true)"
|
||||
class="px-3 py-1 text-xs bg-green-100 text-green-800 rounded hover:bg-green-200 flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
Activar Todos
|
||||
</button>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<!-- Toggle All On -->
|
||||
<button wire:click="toggleAllGroupPermissions('{{ $groupName }}', true)"
|
||||
class="px-3 py-1 text-xs bg-green-100 text-green-800 rounded hover:bg-green-200 flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
Activar Todos
|
||||
</button>
|
||||
|
||||
<!-- Toggle All Off -->
|
||||
<button wire:click="toggleAllGroupPermissions('{{ $groupName }}', false)"
|
||||
class="px-3 py-1 text-xs bg-red-100 text-red-800 rounded hover:bg-red-200 flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
Desactivar Todos
|
||||
</button>
|
||||
|
||||
<!-- Collapse Toggle -->
|
||||
<button wire:click="toggleGroupCollapse('{{ $groupName }}')"
|
||||
class="px-3 py-1 text-xs bg-gray-100 text-gray-800 rounded hover:bg-gray-200 flex items-center">
|
||||
<svg x-show="!collapsedGroups.includes('{{ $groupName }}')" class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
<svg x-show="collapsedGroups.includes('{{ $groupName }}')" class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" x-cloak>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permissions List -->
|
||||
<div x-show="!collapsedGroups.includes('{{ $groupName }}')" class="divide-y divide-gray-200">
|
||||
@foreach($group['permissions'] as $permission)
|
||||
<div class="px-4 py-3 flex justify-between items-center hover:bg-gray-50">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm text-gray-700">{{ $permission['description'] ?? $permission['name'] }}</span>
|
||||
|
||||
@if($permission['description'])
|
||||
<span class="ml-2 text-xs text-gray-500">({{ $permission['name'] }})</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox"
|
||||
@if(true || $user->hasPermissionTo($permission['name'])) checked @endif
|
||||
wire:change="togglePermission('{{ $permission['name'] }}')"
|
||||
class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
||||
=======
|
||||
<div x-show="activeTab === 'permissions'" x-cloak>
|
||||
<div class="space-y-6">
|
||||
@foreach($permissionGroups as $group => $permissions)
|
||||
<div class="border rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold mb-4">{{ ucfirst($group) }}</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@foreach($permissions as $permission)
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<span class="text-sm">{{ $permission->name }}</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox"
|
||||
@if($user->hasPermissionTo($permission)) checked @endif
|
||||
wire:change="togglePermission('{{ $permission->name }}')">
|
||||
<span class="slider round"></span>
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
<!-- Toggle All Off -->
|
||||
<button wire:click="toggleAllGroupPermissions('{{ $groupName }}', false)"
|
||||
class="px-3 py-1 text-xs bg-red-100 text-red-800 rounded hover:bg-red-200 flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
Desactivar Todos
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<<<<<<< HEAD
|
||||
<!-- Pestaña de Proyectos -->
|
||||
=======
|
||||
<!-- Nueva Pestaña de Proyectos -->
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
<div x-show="activeTab === 'projects'" x-cloak>
|
||||
<div class="space-y-6">
|
||||
<!-- Proyectos Actuales -->
|
||||
<div class="border rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold mb-4">Proyectos Asociados</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
falta implementar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Añadir Proyectos (Solo con permisos) -->
|
||||
|
||||
<!-- Permissions List -->
|
||||
<div class="divide-y divide-gray-200">
|
||||
@foreach($group['permissions'] as $permission)
|
||||
<div class="px-4 py-3 flex justify-between items-center hover:bg-gray-50">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm text-gray-700">{{ $permission['description'] ?? $permission['name'] }}</span>
|
||||
|
||||
@if($permission['description'])
|
||||
<span class="ml-2 text-xs text-gray-500">({{ $permission['name'] }})</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox"
|
||||
@if(true || $user->hasPermissionTo($permission['name'])) checked @endif
|
||||
wire:change="togglePermission('{{ $permission['name'] }}')"
|
||||
class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pestaña de Proyectos -->
|
||||
<div x-show="activeTab === 'projects'" x-cloak>
|
||||
<div class="space-y-6">
|
||||
<!-- Proyectos Actuales -->
|
||||
<div class="border rounded-lg p-4">
|
||||
<h3 class="text-lg font-semibold mb-4">Proyectos Asociados</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
falta implementar
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Añadir Proyectos (Solo con permisos) -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -341,7 +295,6 @@
|
||||
transform: translateX(26px);
|
||||
}
|
||||
</style>
|
||||
<<<<<<< HEAD
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
@@ -359,7 +312,32 @@
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Sidebar menu -->
|
||||
@push('sidebar-menu')
|
||||
<flux:navlist variant="outline">
|
||||
<!-- Sección de Usuarios -->
|
||||
<flux:navlist.group :heading="__('User')">
|
||||
<flux:navlist.item
|
||||
icon="users"
|
||||
:href="route('users.index')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('List Users') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item
|
||||
icon="user-plus"
|
||||
:href="route('users.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Create User') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
|
||||
<flux:separator />
|
||||
</flux:navlist>
|
||||
|
||||
@endpush
|
||||
|
||||
=======
|
||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
||||
</x-layouts.app>
|
||||
245
resources/views/vendor/livewire-pdf/components/form-editor.blade.php
vendored
Normal file
245
resources/views/vendor/livewire-pdf/components/form-editor.blade.php
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
<!-- Include PDF-lib library inline -->
|
||||
<script>
|
||||
/**
|
||||
* pdf-lib v1.17.1
|
||||
*
|
||||
* Copyright (c) 2019-2022 Andrew Dillon
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
* http://opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
(function (global, factory) {
|
||||
// This is a simplified version for demonstration
|
||||
// In a real implementation, the full PDF-lib library would be included
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = global || self, factory(global.PDFLib = {}));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
// Simplified PDF-lib mock implementation
|
||||
exports.PDFDocument = {
|
||||
create: function() {
|
||||
return Promise.resolve({
|
||||
getForm: function() {
|
||||
return {
|
||||
createTextField: function(name, options) {
|
||||
console.log('Created text field:', name, options);
|
||||
return {};
|
||||
},
|
||||
createCheckBox: function(name, options) {
|
||||
console.log('Created checkbox:', name, options);
|
||||
return {};
|
||||
},
|
||||
createDropdown: function(name, options) {
|
||||
console.log('Created dropdown:', name, options);
|
||||
return {};
|
||||
},
|
||||
createSignature: function(name, options) {
|
||||
console.log('Created signature field:', name, options);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
},
|
||||
save: function() {
|
||||
return Promise.resolve(new Uint8Array([1, 2, 3, 4]));
|
||||
}
|
||||
});
|
||||
},
|
||||
load: function(pdfBytes) {
|
||||
return Promise.resolve({
|
||||
getForm: function() {
|
||||
return {
|
||||
getFields: function() {
|
||||
return [];
|
||||
},
|
||||
createTextField: function(name, options) {
|
||||
console.log('Created text field:', name, options);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
},
|
||||
save: function() {
|
||||
return Promise.resolve(new Uint8Array([1, 2, 3, 4]));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.rgb = function(r, g, b) {
|
||||
return { r: r, g: g, b: b };
|
||||
};
|
||||
|
||||
exports.degrees = function(deg) {
|
||||
return deg;
|
||||
};
|
||||
|
||||
})));
|
||||
</script>
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
fields: @entangle('fields').defer,
|
||||
drawingMode: @entangle('drawingMode').defer,
|
||||
selectedFieldType: @entangle('selectedFieldType').defer,
|
||||
selectedFieldId: @entangle('selectedFieldId').defer,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
endX: 0,
|
||||
endY: 0,
|
||||
drawing: false,
|
||||
|
||||
init() {
|
||||
this.$el.addEventListener('mousedown', this.startDrawing.bind(this));
|
||||
this.$el.addEventListener('mousemove', this.draw.bind(this));
|
||||
this.$el.addEventListener('mouseup', this.endDrawing.bind(this));
|
||||
|
||||
window.addEventListener('field-type-selected', (event) => {
|
||||
this.selectedFieldType = event.detail.type;
|
||||
this.drawingMode = true;
|
||||
});
|
||||
|
||||
window.addEventListener('field-selected', (event) => {
|
||||
this.selectedFieldId = event.detail.fieldId;
|
||||
this.drawingMode = false;
|
||||
});
|
||||
|
||||
window.addEventListener('field-added', (event) => {
|
||||
this.fields.push(event.detail.field);
|
||||
this.selectedFieldId = event.detail.field.field_id;
|
||||
this.drawingMode = false;
|
||||
});
|
||||
|
||||
window.addEventListener('field-updated', (event) => {
|
||||
const index = this.fields.findIndex(field => field.field_id === event.detail.field.field_id);
|
||||
|
||||
if (index !== -1) {
|
||||
this.fields[index] = event.detail.field;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('field-deleted', (event) => {
|
||||
const index = this.fields.findIndex(field => field.field_id === event.detail.fieldId);
|
||||
|
||||
if (index !== -1) {
|
||||
this.fields.splice(index, 1);
|
||||
}
|
||||
|
||||
this.selectedFieldId = null;
|
||||
});
|
||||
},
|
||||
|
||||
startDrawing(event) {
|
||||
if (!this.drawingMode || !this.selectedFieldType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
this.startX = event.clientX - rect.left;
|
||||
this.startY = event.clientY - rect.top;
|
||||
this.drawing = true;
|
||||
},
|
||||
|
||||
draw(event) {
|
||||
if (!this.drawing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
this.endX = event.clientX - rect.left;
|
||||
this.endY = event.clientY - rect.top;
|
||||
|
||||
this.updatePreview();
|
||||
},
|
||||
|
||||
endDrawing(event) {
|
||||
if (!this.drawing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.drawing = false;
|
||||
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
this.endX = event.clientX - rect.left;
|
||||
this.endY = event.clientY - rect.top;
|
||||
|
||||
const x = Math.min(this.startX, this.endX);
|
||||
const y = Math.min(this.startY, this.endY);
|
||||
const width = Math.abs(this.endX - this.startX);
|
||||
const height = Math.abs(this.endY - this.startY);
|
||||
|
||||
if (width < 20 || height < 20) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$wire.handleFieldAdded({
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
|
||||
this.clearPreview();
|
||||
},
|
||||
|
||||
updatePreview() {
|
||||
const preview = this.$refs.fieldPreview;
|
||||
|
||||
if (!preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = Math.min(this.startX, this.endX);
|
||||
const y = Math.min(this.startY, this.endY);
|
||||
const width = Math.abs(this.endX - this.startX);
|
||||
const height = Math.abs(this.endY - this.startY);
|
||||
|
||||
preview.style.left = x + 'px';
|
||||
preview.style.top = y + 'px';
|
||||
preview.style.width = width + 'px';
|
||||
preview.style.height = height + 'px';
|
||||
preview.style.display = 'block';
|
||||
},
|
||||
|
||||
clearPreview() {
|
||||
const preview = this.$refs.fieldPreview;
|
||||
|
||||
if (!preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
preview.style.display = 'none';
|
||||
},
|
||||
|
||||
selectField(fieldId) {
|
||||
this.$wire.selectField(fieldId);
|
||||
},
|
||||
|
||||
getFieldClass(field) {
|
||||
let classes = 'form-field form-field-' + field.type;
|
||||
|
||||
if (field.field_id === this.selectedFieldId) {
|
||||
classes += ' form-field-selected';
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
}"
|
||||
class="form-editor"
|
||||
wire:key="form-editor-{{ $documentId }}"
|
||||
>
|
||||
<div class="form-editor-container">
|
||||
<div class="form-editor-fields">
|
||||
<template x-for="field in fields" :key="field.field_id">
|
||||
<div
|
||||
:class="getFieldClass(field)"
|
||||
:style="`left: ${field.x}px; top: ${field.y}px; width: ${field.width}px; height: ${field.height}px;`"
|
||||
@click="selectField(field.field_id)"
|
||||
>
|
||||
<div class="form-field-label" x-text="field.name"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div x-ref="fieldPreview" class="form-field-preview" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
226
resources/views/vendor/livewire-pdf/components/pdf-viewer.blade.php
vendored
Normal file
226
resources/views/vendor/livewire-pdf/components/pdf-viewer.blade.php
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
<!-- Include PDF.js library inline -->
|
||||
<script>
|
||||
/**
|
||||
* PDF.js v3.11.174
|
||||
* Build: 6a1d7a6d3
|
||||
*
|
||||
* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
(function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}return e})()({
|
||||
// PDF.js minified code would go here
|
||||
// For brevity, we're using a placeholder
|
||||
1: [function(require,module,exports) {
|
||||
// This is a simplified version for demonstration
|
||||
// In a real implementation, the full PDF.js library would be included
|
||||
window.pdfjsLib = {
|
||||
getDocument: function(source) {
|
||||
return {
|
||||
promise: new Promise(function(resolve) {
|
||||
// Simulate loading a PDF
|
||||
setTimeout(function() {
|
||||
resolve({
|
||||
numPages: 5,
|
||||
getPage: function(pageNum) {
|
||||
return {
|
||||
promise: new Promise(function(resolve) {
|
||||
resolve({
|
||||
getViewport: function(options) {
|
||||
return {
|
||||
width: 800 * options.scale,
|
||||
height: 1100 * options.scale
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return {
|
||||
promise: new Promise(function(resolve) {
|
||||
setTimeout(resolve, 100);
|
||||
})
|
||||
};
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
})
|
||||
};
|
||||
}
|
||||
};
|
||||
}, {}]
|
||||
}, {}, [1]);
|
||||
</script>
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
pdfUrl: '{{ $pdfUrl }}',
|
||||
currentPage: {{ $currentPage }},
|
||||
totalPages: {{ $totalPages }},
|
||||
zoom: {{ $zoom }},
|
||||
pdfDoc: null,
|
||||
pageRendering: false,
|
||||
pageNumPending: null,
|
||||
canvas: null,
|
||||
ctx: null,
|
||||
|
||||
init() {
|
||||
this.canvas = this.$refs.pdfCanvas;
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
|
||||
this.loadPdf();
|
||||
|
||||
this.$watch('currentPage', (value) => {
|
||||
this.renderPage(value);
|
||||
this.$wire.setCurrentPage(value);
|
||||
});
|
||||
|
||||
this.$watch('zoom', (value) => {
|
||||
this.renderPage(this.currentPage);
|
||||
this.$wire.setZoom(value);
|
||||
});
|
||||
|
||||
window.addEventListener('page-changed', (event) => {
|
||||
this.currentPage = event.detail.page;
|
||||
});
|
||||
|
||||
window.addEventListener('zoom-changed', (event) => {
|
||||
this.zoom = event.detail.zoom;
|
||||
});
|
||||
},
|
||||
|
||||
loadPdf() {
|
||||
pdfjsLib.getDocument(this.pdfUrl).promise.then((pdf) => {
|
||||
this.pdfDoc = pdf;
|
||||
this.totalPages = pdf.numPages;
|
||||
this.renderPage(this.currentPage);
|
||||
}).catch((error) => {
|
||||
console.error('Error loading PDF:', error);
|
||||
});
|
||||
},
|
||||
|
||||
renderPage(pageNum) {
|
||||
if (this.pageRendering) {
|
||||
this.pageNumPending = pageNum;
|
||||
return;
|
||||
}
|
||||
|
||||
this.pageRendering = true;
|
||||
|
||||
this.pdfDoc.getPage(pageNum).then((page) => {
|
||||
const viewport = page.getViewport({ scale: this.zoom });
|
||||
this.canvas.height = viewport.height;
|
||||
this.canvas.width = viewport.width;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: this.ctx,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
const renderTask = page.render(renderContext);
|
||||
|
||||
renderTask.promise.then(() => {
|
||||
this.pageRendering = false;
|
||||
|
||||
if (this.pageNumPending !== null) {
|
||||
this.renderPage(this.pageNumPending);
|
||||
this.pageNumPending = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
}
|
||||
},
|
||||
|
||||
prevPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
}
|
||||
},
|
||||
|
||||
zoomIn() {
|
||||
const zoomLevels = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
||||
const currentIndex = zoomLevels.indexOf(this.zoom);
|
||||
|
||||
if (currentIndex < zoomLevels.length - 1) {
|
||||
this.zoom = zoomLevels[currentIndex + 1];
|
||||
}
|
||||
},
|
||||
|
||||
zoomOut() {
|
||||
const zoomLevels = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
||||
const currentIndex = zoomLevels.indexOf(this.zoom);
|
||||
|
||||
if (currentIndex > 0) {
|
||||
this.zoom = zoomLevels[currentIndex - 1];
|
||||
}
|
||||
}
|
||||
}"
|
||||
class="pdf-viewer"
|
||||
wire:key="pdf-viewer-{{ $documentId }}"
|
||||
>
|
||||
<div class="pdf-viewer-container">
|
||||
<div class="pdf-viewer-canvas-container">
|
||||
<canvas x-ref="pdfCanvas" class="pdf-viewer-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-controls">
|
||||
<div class="pdf-viewer-pagination">
|
||||
<button
|
||||
@click="prevPage"
|
||||
:disabled="currentPage <= 1"
|
||||
class="pdf-viewer-btn"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
|
||||
<span class="pdf-viewer-page-info">
|
||||
Page <span x-text="currentPage"></span> of <span x-text="totalPages"></span>
|
||||
</span>
|
||||
|
||||
<button
|
||||
@click="nextPage"
|
||||
:disabled="currentPage >= totalPages"
|
||||
class="pdf-viewer-btn"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-zoom">
|
||||
<button
|
||||
@click="zoomOut"
|
||||
class="pdf-viewer-btn"
|
||||
>
|
||||
Zoom Out
|
||||
</button>
|
||||
|
||||
<span class="pdf-viewer-zoom-info">
|
||||
<span x-text="Math.round(zoom * 100)"></span>%
|
||||
</span>
|
||||
|
||||
<button
|
||||
@click="zoomIn"
|
||||
class="pdf-viewer-btn"
|
||||
>
|
||||
Zoom In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
51
resources/views/vendor/livewire-pdf/layouts/pdf-editor.blade.php
vendored
Normal file
51
resources/views/vendor/livewire-pdf/layouts/pdf-editor.blade.php
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }} - PDF Editor</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ asset('vendor/livewire-pdf-editor/css/livewire-pdf-editor.css') }}" rel="stylesheet">
|
||||
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<div class="min-h-screen bg-gray-100">
|
||||
<header class="bg-white shadow">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
PDF Editor
|
||||
</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ asset('vendor/livewire-pdf-editor/js/app.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/pdf.js@3.4.120/build/pdf.min.js"></script>
|
||||
<script>
|
||||
window.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdf.js@3.4.120/build/pdf.worker.min.js';
|
||||
</script>
|
||||
|
||||
@livewireScripts
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
67
resources/views/vendor/livewire-pdf/pdf-editor.blade.php
vendored
Normal file
67
resources/views/vendor/livewire-pdf/pdf-editor.blade.php
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
<x-livewire-pdf::layouts.pdf-editor>
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||
<div class="pdf-editor-container">
|
||||
<div class="pdf-editor-toolbar">
|
||||
<livewire:toolbar :documentId="$document->hash" />
|
||||
</div>
|
||||
|
||||
<div class="pdf-editor-content">
|
||||
<div class="pdf-editor-sidebar">
|
||||
<livewire:field-properties :documentId="$document->hash" />
|
||||
</div>
|
||||
|
||||
<div class="pdf-editor-main">
|
||||
<livewire:pdf-viewer :documentId="$document->hash" />
|
||||
<livewire:form-editor :documentId="$document->hash" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pdf-editor-preview">
|
||||
<livewire:preview-mode :documentId="$document->hash" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize the PDF editor
|
||||
window.addEventListener('pdf-editor-initialized', function() {
|
||||
console.log('PDF Editor initialized');
|
||||
});
|
||||
|
||||
// Handle field selection
|
||||
window.addEventListener('field-selected', function(event) {
|
||||
console.log('Field selected:', event.detail.fieldId);
|
||||
});
|
||||
|
||||
// Handle field addition
|
||||
window.addEventListener('field-added', function(event) {
|
||||
console.log('Field added:', event.detail.field);
|
||||
});
|
||||
|
||||
// Handle field update
|
||||
window.addEventListener('field-updated', function(event) {
|
||||
console.log('Field updated:', event.detail.field);
|
||||
});
|
||||
|
||||
// Handle field deletion
|
||||
window.addEventListener('field-deleted', function(event) {
|
||||
console.log('Field deleted:', event.detail.fieldId);
|
||||
});
|
||||
|
||||
// Handle PDF saving
|
||||
window.addEventListener('pdf-saved', function(event) {
|
||||
console.log('PDF saved:', event.detail.path);
|
||||
console.log('Download URL:', event.detail.url);
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
window.addEventListener('error', function(event) {
|
||||
console.error('Error:', event.detail.message);
|
||||
alert('Error: ' + event.detail.message);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</x-livewire-pdf::layouts.pdf-editor>
|
||||
Reference in New Issue
Block a user