first&last

This commit is contained in:
sbb45
2026-03-20 15:47:17 +05:00
commit d6441abdd2
230 changed files with 20367 additions and 0 deletions
@@ -0,0 +1,19 @@
<x-layouts.guest>
<form method="POST" action="{{ route('password.email') }}" class="space-y-4">
@csrf
<p class="text-sm text-slate-600">Введите email, и мы отправим ссылку для сброса пароля.</p>
@if (session('status'))
<div class="rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm text-emerald-700">{{ session('status') }}</div>
@endif
<div>
<label class="block text-sm font-medium mb-1">Email</label>
<input name="email" type="email" class="w-full rounded-xl border border-slate-300 px-4 py-2.5" required>
@error('email') <p class="text-xs text-rose-600 mt-1">{{ $message }}</p> @enderror
</div>
<button class="w-full rounded-xl bg-slate-900 text-white py-2.5">Отправить ссылку</button>
<a href="{{ route('login') }}" class="block text-center text-sm text-slate-600">Вернуться ко входу</a>
</form>
</x-layouts.guest>
@@ -0,0 +1,23 @@
<x-layouts.guest>
<form method="POST" action="{{ route('password.store') }}" class="space-y-4">
@csrf
<input type="hidden" name="token" value="{{ $request->route('token') }}">
<div>
<label class="block text-sm font-medium mb-1">Email</label>
<input name="email" value="{{ old('email', $request->email) }}" type="email" class="w-full rounded-xl border border-slate-300 px-4 py-2.5" required>
@error('email') <p class="text-xs text-rose-600 mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-1">Новый пароль</label>
<input name="password" type="password" class="w-full rounded-xl border border-slate-300 px-4 py-2.5" required>
@error('password') <p class="text-xs text-rose-600 mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-1">Подтверждение пароля</label>
<input name="password_confirmation" type="password" class="w-full rounded-xl border border-slate-300 px-4 py-2.5" required>
</div>
</form>
</x-layouts.guest>
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Computer Service' }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
@fluxAppearance
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-slate-100 text-slate-900 antialiased">
<div class="min-h-screen flex items-center justify-center p-6">
<div class="w-full max-w-md rounded-2xl bg-white shadow-xl border border-slate-200 p-8">
<div class="mb-6 text-center">
<h1 class="text-2xl font-semibold tracking-tight">Computer Service</h1>
<p class="text-sm text-slate-500 mt-1">Сервисная платформа поддержки</p>
</div>
{{ $slot }}
</div>
</div>
@livewireScripts
@fluxScripts
</body>
</html>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<style>
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #d1d5db; padding: 6px; text-align: left; }
th { background: #f3f4f6; }
</style>
</head>
<body>
<h2>Отчет по эффективности техников</h2>
<p>Сформировано: {{ $generatedAt }}</p>
<table>
<thead>
<tr>
<th>Техник</th>
<th>Назначено</th>
<th>Решено</th>
<th>Среднее время (ч)</th>
<th>Рейтинг</th>
<th>Просрочено</th>
</tr>
</thead>
<tbody>
@foreach($rows as $row)
<tr>
<td>{{ $row['technician'] }}</td>
<td>{{ $row['assigned'] }}</td>
<td>{{ $row['resolved'] }}</td>
<td>{{ $row['avg_resolution_hours'] }}</td>
<td>{{ $row['rating'] }}</td>
<td>{{ $row['overdue'] }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>
+152
View File
@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="ru" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Computer Service' }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
@if(request()->routeIs('dashboard', 'reports.general'))
<script src="https://cdn.jsdelivr.net/npm/chart.js" defer></script>
@endif
@fluxAppearance
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body class="h-full bg-slate-100 text-slate-900">
<div x-data="{ sidebarOpen: false }" class="h-full">
<div class="fixed inset-y-0 left-0 z-40 w-72 transform bg-slate-900 text-slate-100 shadow-xl transition-transform duration-200 lg:translate-x-0"
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'">
<div class="h-full flex flex-col">
<div class="px-5 py-5 border-b border-slate-800">
<div class="text-lg font-semibold tracking-tight">{{ config('app.name', 'Computer Service') }}</div>
<div class="text-xs text-slate-400 mt-1">IT Helpdesk & Service Desk</div>
</div>
<nav class="flex-1 overflow-y-auto p-3 space-y-1 text-sm">
@php
$user = auth()->user();
$isAdmin = $user?->isAdmin();
$isManager = $user?->isManager();
$isTechnician = $user?->isTechnician();
$isClient = $user?->isClient();
@endphp
<p class="px-3 py-1 text-xs uppercase text-slate-400">Главная</p>
@if($isAdmin || $isManager)
<a href="{{ route('dashboard') }}" class="nav-link {{ request()->routeIs('dashboard') ? 'nav-link-active' : '' }}">Дашборд</a>
@endif
<p class="px-3 pt-4 pb-1 text-xs uppercase text-slate-400">Заявки</p>
@if($isAdmin || $isManager)
<a href="{{ route('requests.index') }}" class="nav-link {{ request()->routeIs('requests.index') ? 'nav-link-active' : '' }}">Все заявки</a>
<a href="{{ route('requests.create') }}" class="nav-link {{ request()->routeIs('requests.create') ? 'nav-link-active' : '' }}">Создать заявку</a>
@endif
@if($isTechnician)
<a href="{{ route('my-tasks') }}" class="nav-link {{ request()->routeIs('my-tasks') ? 'nav-link-active' : '' }}">Мои задачи</a>
@endif
@if($isClient)
<a href="{{ route('my-requests.index') }}" class="nav-link {{ request()->routeIs('my-requests.*') ? 'nav-link-active' : '' }}">Мои заявки</a>
@endif
@if($isAdmin)
<p class="px-3 pt-4 pb-1 text-xs uppercase text-slate-400">Справочники</p>
<a href="{{ route('directories.departments') }}" class="nav-link {{ request()->routeIs('directories.departments') ? 'nav-link-active' : '' }}">Отделы</a>
<a href="{{ route('directories.equipment-categories') }}" class="nav-link {{ request()->routeIs('directories.equipment-categories') ? 'nav-link-active' : '' }}">Категории оборудования</a>
<a href="{{ route('directories.request-types') }}" class="nav-link {{ request()->routeIs('directories.request-types') ? 'nav-link-active' : '' }}">Типы заявок</a>
<a href="{{ route('directories.equipment') }}" class="nav-link {{ request()->routeIs('directories.equipment') ? 'nav-link-active' : '' }}">Оборудование</a>
@endif
@if($isAdmin || $isManager)
<p class="px-3 pt-4 pb-1 text-xs uppercase text-slate-400">Отчеты</p>
<a href="{{ route('reports.general') }}" class="nav-link {{ request()->routeIs('reports.general') ? 'nav-link-active' : '' }}">Общий</a>
<a href="{{ route('reports.technicians') }}" class="nav-link {{ request()->routeIs('reports.technicians') ? 'nav-link-active' : '' }}">Техники</a>
<a href="{{ route('reports.sla') }}" class="nav-link {{ request()->routeIs('reports.sla') ? 'nav-link-active' : '' }}">SLA</a>
<a href="{{ route('reports.equipment') }}" class="nav-link {{ request()->routeIs('reports.equipment') ? 'nav-link-active' : '' }}">Оборудование</a>
@endif
@if($isAdmin)
<p class="px-3 pt-4 pb-1 text-xs uppercase text-slate-400">Система</p>
<a href="{{ route('users.index') }}" class="nav-link {{ request()->routeIs('users.*') ? 'nav-link-active' : '' }}">Пользователи</a>
<a href="{{ route('activity-log') }}" class="nav-link {{ request()->routeIs('activity-log') ? 'nav-link-active' : '' }}">Журнал действий</a>
<a href="{{ route('settings') }}" class="nav-link {{ request()->routeIs('settings') ? 'nav-link-active' : '' }}">Настройки</a>
@endif
</nav>
<div class="border-t border-slate-800 p-4">
<div class="flex items-center gap-3 mb-3">
<div class="h-10 w-10 rounded-full bg-slate-700 flex items-center justify-center text-xs font-semibold">
{{ mb_substr(auth()->user()->name, 0, 1) }}
</div>
<div class="min-w-0">
<p class="text-sm font-medium truncate">{{ auth()->user()->name }}</p>
<p class="text-xs text-slate-400">{{ auth()->user()->role->label() }}</p>
</div>
</div>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="w-full rounded-lg bg-slate-800 hover:bg-slate-700 px-3 py-2 text-sm transition">Выйти</button>
</form>
</div>
</div>
</div>
<div class="lg:pl-72 min-h-full">
<header class="sticky top-0 z-30 border-b border-slate-200 bg-white/95 backdrop-blur">
<div class="flex items-center justify-between px-4 py-3 lg:px-8">
<button @click="sidebarOpen = !sidebarOpen" class="lg:hidden rounded-md border border-slate-300 px-3 py-2 text-sm">Меню</button>
<div class="text-sm text-slate-500">
{{ $breadcrumb ?? 'Рабочая область' }}
</div>
<div class="flex items-center gap-3">
<div x-data="{ open: false }" class="relative">
<button @click="open = !open" class="relative rounded-lg border border-slate-200 px-3 py-2 text-sm hover:bg-slate-50">
Уведомления
@if(auth()->user()->unreadNotifications()->count() > 0)
<span class="absolute -top-2 -right-2 bg-rose-500 text-white text-[10px] rounded-full min-w-[18px] h-[18px] px-1 flex items-center justify-center">
{{ auth()->user()->unreadNotifications()->count() }}
</span>
@endif
</button>
<div x-cloak x-show="open" @click.outside="open = false" class="absolute right-0 mt-2 w-96 rounded-xl border border-slate-200 bg-white shadow-xl p-3">
<div class="flex items-center justify-between mb-2">
<p class="text-sm font-semibold">Последние уведомления</p>
<a href="{{ route('notifications.index') }}" class="text-xs text-slate-600">Все</a>
</div>
<div class="space-y-2 max-h-80 overflow-y-auto">
@forelse(auth()->user()->notifications()->latest()->limit(10)->get() as $notification)
<a href="{{ $notification->data['url'] ?? route('notifications.index') }}" class="block rounded-lg border border-slate-100 px-3 py-2 hover:bg-slate-50">
<p class="text-sm font-medium">{{ $notification->data['title'] ?? 'Уведомление' }}</p>
<p class="text-xs text-slate-500 mt-1">{{ $notification->data['message'] ?? '' }}</p>
</a>
@empty
<p class="text-sm text-slate-500">Новых уведомлений нет.</p>
@endforelse
</div>
</div>
</div>
<a href="{{ route('profile') }}" class="rounded-lg border border-slate-200 px-3 py-2 text-sm hover:bg-slate-50">Профиль</a>
</div>
</div>
</header>
<main class="p-4 lg:p-8 space-y-4">
@if(session()->has('success'))
<div class="rounded-lg border border-emerald-200 bg-emerald-50 text-emerald-700 px-4 py-3 text-sm">{{ session('success') }}</div>
@endif
@if(session()->has('error'))
<div class="rounded-lg border border-rose-200 bg-rose-50 text-rose-700 px-4 py-3 text-sm">{{ session('error') }}</div>
@endif
{{ $slot }}
</main>
</div>
</div>
@livewireScripts
@fluxScripts
@stack('scripts')
</body>
</html>
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? 'Computer Service' }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
@fluxAppearance
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body class="bg-slate-100 text-slate-900 antialiased">
<div class="min-h-screen flex items-center justify-center p-6">
<div class="w-full max-w-md rounded-2xl bg-white shadow-xl border border-slate-200 p-8">
<div class="mb-6 text-center">
<h1 class="text-2xl font-semibold tracking-tight">ServiceAir</h1>
<p class="text-sm text-slate-500 mt-1">Сервисная платформа поддержки</p>
</div>
{{ $slot }}
</div>
</div>
@livewireScripts
@fluxScripts
</body>
</html>
@@ -0,0 +1,29 @@
<div>
<form wire:submit="login" class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Email</label>
<input wire:model.defer="email" type="email" class="w-full rounded-xl border border-slate-300 px-4 py-2.5 focus:border-slate-900 focus:ring-0" placeholder="name@service.local">
@error('email') <p class="text-xs text-rose-600 mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-1">Пароль</label>
<div class="relative">
<input wire:model.defer="password" type="{{ $showPassword ? 'text' : 'password' }}" class="w-full rounded-xl border border-slate-300 px-4 py-2.5 pr-24 focus:border-slate-900 focus:ring-0" placeholder="Введите пароль">
<button type="button" wire:click="$toggle('showPassword')" class="absolute right-2 top-1/2 -translate-y-1/2 text-xs rounded-lg border border-slate-300 px-2.5 py-1">{{ $showPassword ? 'Скрыть' : 'Показать' }}</button>
</div>
@error('password') <p class="text-xs text-rose-600 mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex items-center justify-between">
<label class="inline-flex items-center gap-2 text-sm">
<input wire:model="remember" type="checkbox" class="rounded border-slate-300">
Запомнить меня
</label>
</div>
<button type="submit" class="w-full rounded-xl bg-slate-900 text-white py-2.5 font-medium hover:bg-slate-800 transition">
Войти
</button>
</form>
</div>
@@ -0,0 +1,84 @@
<div wire:poll.60s="refreshData" class="space-y-6">
<div id="dashboard-chart-data" class="hidden"
data-status='@json($statusChart)'
data-priority='@json($priorityChart)'></div>
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Открыто сегодня</p><p class="text-3xl font-semibold mt-2">{{ $openToday }}</p></div></div>
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Просрочено</p><p class="text-3xl font-semibold mt-2 text-rose-600">{{ $overdue }}</p></div></div>
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Среднее время решения (30 дней)</p><p class="text-3xl font-semibold mt-2">{{ number_format($avgResolutionHours, 1) }}ч</p></div></div>
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Топ техников</p><p class="text-3xl font-semibold mt-2">{{ count($topTechnicians) }}</p></div></div>
</div>
<div class="grid gap-6 xl:grid-cols-2">
<div class="card" wire:ignore>
<div class="card-body">
<h2 class="text-lg font-semibold mb-4">Заявки по статусам</h2>
<div class="dashboard-chart-wrap">
<canvas id="statusChart"></canvas>
</div>
</div>
</div>
<div class="card" wire:ignore>
<div class="card-body">
<h2 class="text-lg font-semibold mb-4">Заявки по приоритетам</h2>
<div class="dashboard-chart-wrap">
<canvas id="priorityChart"></canvas>
</div>
</div>
</div>
</div>
<div class="grid gap-6 xl:grid-cols-2">
<div class="card">
<div class="card-body">
<h2 class="text-lg font-semibold mb-4">Топ-5 техников</h2>
<div class="space-y-2">
@forelse($topTechnicians as $item)
<div class="flex items-center justify-between rounded-lg border border-slate-200 px-3 py-2">
<span>{{ $item['name'] }}</span>
<span class="text-sm text-slate-500">Закрыто: {{ $item['closed_requests_count'] }}</span>
</div>
@empty
<p class="text-sm text-slate-500">Нет данных.</p>
@endforelse
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h2 class="text-lg font-semibold mb-4">Ближайшие плановые работы</h2>
<div class="space-y-2">
@forelse($upcomingTasks as $item)
<div class="rounded-lg border border-slate-200 px-3 py-2">
<p class="font-medium text-sm">{{ $item['ticket'] }} {{ $item['title'] }}</p>
<p class="text-xs text-slate-500 mt-1">{{ $item['planned_at'] }} {{ $item['assignee'] }}</p>
</div>
@empty
<p class="text-sm text-slate-500">Нет запланированных задач.</p>
@endforelse
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h2 class="text-lg font-semibold mb-4">Лента активности</h2>
<div class="space-y-2">
@forelse($recentActivities as $item)
<div class="rounded-lg border border-slate-200 px-3 py-2 flex justify-between items-start gap-4">
<div>
<p class="text-sm font-medium">{{ $item['user'] }}: {{ $item['description'] }}</p>
<p class="text-xs text-slate-500">{{ $item['action'] }}</p>
</div>
<p class="text-xs text-slate-500 whitespace-nowrap">{{ $item['created_at'] }}</p>
</div>
@empty
<p class="text-sm text-slate-500">Активность отсутствует.</p>
@endforelse
</div>
</div>
</div>
</div>
@@ -0,0 +1,11 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card lg:col-span-1"><div class="card-body">
<h2 class="text-lg font-semibold mb-3">{{ $editingId ? 'Редактировать отдел' : 'Новый отдел' }}</h2>
<div class="space-y-3">
<div><label class="text-sm font-medium">Название</label><input wire:model.defer="name" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Описание</label><textarea wire:model.defer="description" rows="3" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></textarea></div>
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div>
</div></div>
<div class="card lg:col-span-2"><div class="card-body"><table class="min-w-full text-sm"><thead><tr><th class="text-left py-2">Название</th><th class="text-left py-2">Описание</th><th></th></tr></thead><tbody>@foreach($items as $item)<tr class="border-t border-slate-100"><td class="py-2">{{ $item->name }}</td><td class="py-2">{{ $item->description }}</td><td class="py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td></tr>@endforeach</tbody></table></div></div>
</div>
@@ -0,0 +1,9 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card"><div class="card-body space-y-3">
<h2 class="text-lg font-semibold">{{ $editingId ? 'Редактировать категорию' : 'Новая категория' }}</h2>
<input wire:model.defer="name" placeholder="Название" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<textarea wire:model.defer="description" rows="3" placeholder="Описание" class="w-full rounded-xl border border-slate-300 px-3 py-2"></textarea>
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div></div>
<div class="card lg:col-span-2"><div class="card-body"><table class="min-w-full text-sm"><thead><tr><th class="text-left py-2">Название</th><th class="text-left py-2">Описание</th><th></th></tr></thead><tbody>@foreach($items as $item)<tr class="border-t border-slate-100"><td class="py-2">{{ $item->name }}</td><td class="py-2">{{ $item->description }}</td><td class="py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td></tr>@endforeach</tbody></table></div></div>
</div>
@@ -0,0 +1,36 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card"><div class="card-body space-y-3">
<h2 class="text-lg font-semibold">{{ $editingId ? 'Редактировать оборудование' : 'Новое оборудование' }}</h2>
<input wire:model.defer="name" placeholder="Название" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="serialNumber" placeholder="Серийный номер" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="inventoryNumber" placeholder="Инвентарный номер" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<select wire:model.defer="categoryId" class="w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Категория</option>@foreach($categories as $c)<option value="{{ $c->id }}">{{ $c->name }}</option>@endforeach</select>
<select wire:model.defer="clientId" class="w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Клиент</option>@foreach($clients as $c)<option value="{{ $c->id }}">{{ $c->name }}</option>@endforeach</select>
<div class="grid gap-2 md:grid-cols-2"><input wire:model.defer="purchaseDate" type="date" class="w-full rounded-xl border border-slate-300 px-3 py-2"><input wire:model.defer="warrantyUntil" type="date" class="w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<select wire:model.defer="condition" class="w-full rounded-xl border border-slate-300 px-3 py-2"><option value="good">Хорошее</option><option value="fair">Удовлетворительное</option><option value="poor">Плохое</option></select>
<textarea wire:model.defer="notes" rows="3" placeholder="Примечания" class="w-full rounded-xl border border-slate-300 px-3 py-2"></textarea>
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div></div>
<div class="card lg:col-span-2 overflow-hidden">
<table class="min-w-full text-sm">
<thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Название</th><th class="px-3 py-2 text-left">Категория</th><th class="px-3 py-2 text-left">Клиент</th><th class="px-3 py-2 text-left">Гарантия</th><th></th></tr></thead>
<tbody>
@foreach($items as $item)
@php
$expired = $item->warranty_until && $item->warranty_until->isPast();
$expiring = $item->warranty_until && !$expired && $item->warranty_until->lte(now()->addDays(30));
@endphp
<tr class="border-t border-slate-100">
<td class="px-3 py-2"><p class="font-medium">{{ $item->name }}</p><p class="text-xs text-slate-500">{{ $item->inventory_number }}</p></td>
<td class="px-3 py-2">{{ $item->category?->name }}</td>
<td class="px-3 py-2">{{ $item->client?->name ?? '—' }}</td>
<td class="px-3 py-2 {{ $expired ? 'text-rose-600' : ($expiring ? 'text-amber-600' : '') }}">{{ $item->warranty_until?->format('d.m.Y') ?? '—' }}</td>
<td class="px-3 py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td>
</tr>
@endforeach
</tbody>
</table>
<div class="p-4">{{ $items->links() }}</div>
</div>
</div>
@@ -0,0 +1,10 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card"><div class="card-body space-y-3">
<h2 class="text-lg font-semibold">{{ $editingId ? 'Редактировать приоритет' : 'Новый приоритет' }}</h2>
<input wire:model.defer="name" placeholder="Название" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="colorHex" type="color" class="w-full rounded-xl border border-slate-300 px-3 py-2 h-10">
<input wire:model.defer="level" type="number" min="1" max="4" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div></div>
<div class="card lg:col-span-2"><div class="card-body"><table class="min-w-full text-sm"><thead><tr><th class="text-left py-2">Название</th><th class="text-left py-2">Цвет</th><th class="text-left py-2">Уровень</th><th></th></tr></thead><tbody>@foreach($items as $item)<tr class="border-t border-slate-100"><td class="py-2">{{ $item->name }}</td><td class="py-2"><span class="inline-block h-4 w-8 rounded" style="background-color: {{ $item->color_hex }}"></span></td><td class="py-2">{{ $item->level }}</td><td class="py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td></tr>@endforeach</tbody></table></div></div>
</div>
@@ -0,0 +1,10 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card"><div class="card-body space-y-3">
<h2 class="text-lg font-semibold">{{ $editingId ? 'Редактировать тип' : 'Новый тип' }}</h2>
<input wire:model.defer="name" placeholder="Название" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="slaHours" type="number" min="1" placeholder="SLA часов" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<textarea wire:model.defer="description" rows="3" placeholder="Описание" class="w-full rounded-xl border border-slate-300 px-3 py-2"></textarea>
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div></div>
<div class="card lg:col-span-2"><div class="card-body"><table class="min-w-full text-sm"><thead><tr><th class="text-left py-2">Название</th><th class="text-left py-2">SLA</th><th class="text-left py-2">Описание</th><th></th></tr></thead><tbody>@foreach($items as $item)<tr class="border-t border-slate-100"><td class="py-2">{{ $item->name }}</td><td class="py-2">{{ $item->sla_hours }} ч</td><td class="py-2">{{ $item->description }}</td><td class="py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td></tr>@endforeach</tbody></table></div></div>
</div>
@@ -0,0 +1,12 @@
<div class="grid gap-4 lg:grid-cols-3">
<div class="card"><div class="card-body space-y-3">
<h2 class="text-lg font-semibold">{{ $editingId ? 'Редактировать статус' : 'Новый статус' }}</h2>
<input wire:model.defer="name" placeholder="Название" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="slug" placeholder="Slug" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.defer="colorHex" type="color" class="w-full rounded-xl border border-slate-300 px-3 py-2 h-10">
<input wire:model.defer="sortOrder" type="number" min="1" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<label class="inline-flex items-center gap-2"><input wire:model.defer="isFinal" type="checkbox" class="rounded"> <span class="text-sm">Финальный</span></label>
<button wire:click="save" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить</button>
</div></div>
<div class="card lg:col-span-2"><div class="card-body"><table class="min-w-full text-sm"><thead><tr><th class="text-left py-2">Название</th><th class="text-left py-2">Slug</th><th class="text-left py-2">Порядок</th><th class="text-left py-2">Финальный</th><th></th></tr></thead><tbody>@foreach($items as $item)<tr class="border-t border-slate-100"><td class="py-2">{{ $item->name }}</td><td class="py-2">{{ $item->slug }}</td><td class="py-2">{{ $item->sort_order }}</td><td class="py-2">{{ $item->is_final ? 'Да' : 'Нет' }}</td><td class="py-2 text-right"><button wire:click="edit({{ $item->id }})" class="text-xs mr-2">Изм.</button><button wire:click="delete({{ $item->id }})" class="text-xs text-rose-600">Удалить</button></td></tr>@endforeach</tbody></table></div></div>
</div>
@@ -0,0 +1,12 @@
<div class="card"><div class="card-body">
<form wire:submit="save" class="space-y-4">
<div><label class="text-sm font-medium">Заголовок</label><input wire:model.defer="title" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">@error('title')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div><label class="text-sm font-medium">Описание</label><textarea wire:model.defer="description" rows="5" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></textarea>@error('description')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div class="grid gap-4 md:grid-cols-3">
<div><label class="text-sm font-medium">Тип заявки</label><select wire:model.defer="typeId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Выберите</option>@foreach($types as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Приоритет</label><select wire:model.defer="priorityId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Выберите</option>@foreach($priorities as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Оборудование</label><select wire:model.defer="equipmentId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Не выбрано</option>@foreach($equipmentItems as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
</div>
<div class="flex justify-end"><button class="rounded-xl bg-slate-900 text-white px-4 py-2">Создать заявку</button></div>
</form>
</div></div>
@@ -0,0 +1,27 @@
<div class="space-y-4">
<div class="flex justify-between items-center">
<input wire:model.live.debounce.300ms="search" type="text" placeholder="Поиск по заявкам" class="rounded-xl border border-slate-300 px-3 py-2 w-full max-w-sm">
<a href="{{ route('my-requests.create') }}" class="rounded-xl bg-slate-900 text-white px-4 py-2 text-sm">Новая заявка</a>
</div>
<div class="card overflow-hidden">
<table class="min-w-full text-sm">
<thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Ticket</th><th class="px-3 py-2 text-left">Заголовок</th><th class="px-3 py-2 text-left">Тип</th><th class="px-3 py-2 text-left">Приоритет</th><th class="px-3 py-2 text-left">Статус</th><th class="px-3 py-2 text-left">Дата</th></tr></thead>
<tbody>
@forelse($requests as $request)
<tr class="border-t border-slate-100">
<td class="px-3 py-2"><a href="{{ route('my-requests.show', $request) }}" class="font-medium hover:underline">{{ $request->ticket_number }}</a></td>
<td class="px-3 py-2">{{ $request->title }}</td>
<td class="px-3 py-2">{{ $request->type?->name }}</td>
<td class="px-3 py-2"><span class="inline-flex rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->priority?->color_hex }}">{{ $request->priority?->name }}</span></td>
<td class="px-3 py-2"><span class="inline-flex rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->status?->color_hex }}">{{ $request->status?->name }}</span></td>
<td class="px-3 py-2">{{ $request->created_at?->format('d.m.Y H:i') }}</td>
</tr>
@empty
<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">Нет заявок</td></tr>
@endforelse
</tbody>
</table>
<div class="p-4">{{ $requests->links() }}</div>
</div>
</div>
@@ -0,0 +1,4 @@
<div class="space-y-4">
<div class="flex justify-end"><button wire:click="markAllAsRead" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">Отметить все как прочитанные</button></div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Уведомление</th><th class="px-3 py-2 text-left">Дата</th><th></th></tr></thead><tbody>@forelse($notifications as $notification)<tr class="border-t border-slate-100 {{ $notification->read_at ? '' : 'bg-blue-50' }}"><td class="px-3 py-2"><p class="font-medium">{{ $notification->data['title'] ?? 'Событие' }}</p><p class="text-xs text-slate-500">{{ $notification->data['message'] ?? '' }}</p></td><td class="px-3 py-2">{{ $notification->created_at?->format('d.m.Y H:i') }}</td><td class="px-3 py-2 text-right">@if(!$notification->read_at)<button wire:click="markAsRead('{{ $notification->id }}')" class="text-xs">Прочитано</button>@endif @if(!empty($notification->data['url']))<a href="{{ $notification->data['url'] }}" class="text-xs ml-2 text-slate-700">Открыть</a>@endif</td></tr>@empty<tr><td colspan="3" class="px-3 py-8 text-center text-slate-500">Нет уведомлений</td></tr>@endforelse</tbody></table><div class="p-4">{{ $notifications->links() }}</div></div>
</div>
@@ -0,0 +1,12 @@
<div class="card max-w-2xl"><div class="card-body">
<form wire:submit="save" class="space-y-4">
<div class="grid gap-4 md:grid-cols-2">
<div><label class="text-sm font-medium">Имя</label><input wire:model.defer="name" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Email</label><input wire:model.defer="email" type="email" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Телефон</label><input wire:model.defer="phone" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Новый пароль</label><input wire:model.defer="password" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Подтверждение</label><input wire:model.defer="password_confirmation" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
</div>
<div class="flex justify-end"><button class="rounded-xl bg-slate-900 text-white px-4 py-2">Сохранить</button></div>
</form>
</div></div>
@@ -0,0 +1,4 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex flex-wrap gap-2"><input wire:model.live="dateFrom" type="date" class="rounded-xl border border-slate-300 px-3 py-2"><input wire:model.live="dateTo" type="date" class="rounded-xl border border-slate-300 px-3 py-2"></div></div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Оборудование</th><th class="px-3 py-2 text-center">Всего обращений</th><th class="px-3 py-2 text-center">Открытые</th><th class="px-3 py-2 text-center">Закрытые</th></tr></thead><tbody>@foreach($rows as $row)<tr class="border-t border-slate-100"><td class="px-3 py-2">{{ $row['equipment'] }}</td><td class="px-3 py-2 text-center">{{ $row['total_requests'] }}</td><td class="px-3 py-2 text-center">{{ $row['open'] }}</td><td class="px-3 py-2 text-center">{{ $row['closed'] }}</td></tr>@endforeach</tbody></table></div>
</div>
@@ -0,0 +1,20 @@
<div class="space-y-4">
<div id="general-report-chart-data" class="hidden" data-chart='@json($statusChart->values()->toArray())'></div>
<div class="card"><div class="card-body grid gap-3 md:grid-cols-5">
<input wire:model.live="dateFrom" type="date" class="rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.live="dateTo" type="date" class="rounded-xl border border-slate-300 px-3 py-2">
<select wire:model.live="technicianId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все техники</option>@foreach($technicians as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>
<select wire:model.live="typeId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все типы</option>@foreach($types as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>
<select wire:model.live="statusId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все статусы</option>@foreach($statuses as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>
</div></div>
<div class="card" wire:ignore>
<div class="card-body">
<h2 class="text-lg font-semibold mb-3">Динамика по статусам</h2>
<canvas id="generalStatusChart" height="120"></canvas>
</div>
</div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Ticket</th><th class="px-3 py-2 text-left">Заголовок</th><th class="px-3 py-2 text-left">Тип</th><th class="px-3 py-2 text-left">Статус</th><th class="px-3 py-2 text-left">Техник</th><th class="px-3 py-2 text-left">Дата</th></tr></thead><tbody>@foreach($rows as $row)<tr class="border-t border-slate-100"><td class="px-3 py-2">{{ $row->ticket_number }}</td><td class="px-3 py-2">{{ $row->title }}</td><td class="px-3 py-2">{{ $row->type?->name }}</td><td class="px-3 py-2">{{ $row->status?->name }}</td><td class="px-3 py-2">{{ $row->assignee?->name ?? '—' }}</td><td class="px-3 py-2">{{ $row->created_at?->format('d.m.Y H:i') }}</td></tr>@endforeach</tbody></table></div>
</div>
@@ -0,0 +1,4 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex flex-wrap gap-2"><input wire:model.live="dateFrom" type="date" class="rounded-xl border border-slate-300 px-3 py-2"><input wire:model.live="dateTo" type="date" class="rounded-xl border border-slate-300 px-3 py-2"></div></div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Тип заявки</th><th class="px-3 py-2 text-center">Всего</th><th class="px-3 py-2 text-center">В SLA</th><th class="px-3 py-2 text-center">Соблюдение</th></tr></thead><tbody>@foreach($rows as $row)<tr class="border-t border-slate-100"><td class="px-3 py-2">{{ $row['type'] }}</td><td class="px-3 py-2 text-center">{{ $row['total'] }}</td><td class="px-3 py-2 text-center">{{ $row['within_sla'] }}</td><td class="px-3 py-2 text-center">{{ $row['percent'] }}%</td></tr>@endforeach</tbody></table></div>
</div>
@@ -0,0 +1,9 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex flex-wrap items-center gap-2">
<input wire:model.live="dateFrom" type="date" class="rounded-xl border border-slate-300 px-3 py-2">
<input wire:model.live="dateTo" type="date" class="rounded-xl border border-slate-300 px-3 py-2">
<button wire:click="exportCsv" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">Экспорт CSV</button>
<button wire:click="exportPdf" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Экспорт PDF</button>
</div></div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Техник</th><th class="px-3 py-2">Назначено</th><th class="px-3 py-2">Решено</th><th class="px-3 py-2">Среднее время</th><th class="px-3 py-2">Рейтинг</th><th class="px-3 py-2">Просрочено</th></tr></thead><tbody>@foreach($rows as $row)<tr class="border-t border-slate-100"><td class="px-3 py-2">{{ $row['technician'] }}</td><td class="px-3 py-2 text-center">{{ $row['assigned'] }}</td><td class="px-3 py-2 text-center">{{ $row['resolved'] }}</td><td class="px-3 py-2 text-center">{{ $row['avg_resolution_hours'] }}ч</td><td class="px-3 py-2 text-center">{{ $row['rating'] }}</td><td class="px-3 py-2 text-center">{{ $row['overdue'] }}</td></tr>@endforeach</tbody></table></div>
</div>
@@ -0,0 +1,22 @@
<div class="card"><div class="card-body">
<form wire:submit="save" class="space-y-4">
<div class="grid gap-4 md:grid-cols-2">
<div class="md:col-span-2"><label class="text-sm font-medium">Заголовок</label><input wire:model.defer="title" type="text" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">@error('title')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div class="md:col-span-2"><label class="text-sm font-medium">Описание</label><textarea wire:model.defer="description" rows="4" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></textarea>@error('description')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div><label class="text-sm font-medium">Тип</label><select wire:model.live="typeId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Выберите</option>@foreach($types as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>@error('type_id')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div><label class="text-sm font-medium">Приоритет</label><select wire:model.defer="priorityId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Выберите</option>@foreach($priorities as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>@error('priority_id')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div><label class="text-sm font-medium">Клиент</label><select wire:model.live="clientId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Выберите</option>@foreach($clients as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select>@error('client_id')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
<div><label class="text-sm font-medium">Исполнитель</label><select wire:model.defer="assignedTo" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Не назначен</option>@foreach($technicians as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Оборудование</label><select wire:model.defer="equipmentId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Не выбрано</option>@foreach($equipmentItems as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Статус</label><select wire:model.defer="statusId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">@foreach($statuses as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Локация</label><input wire:model.defer="location" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Плановая дата</label><input wire:model.defer="plannedAt" type="datetime-local" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Дедлайн</label><input wire:model.defer="deadlineAt" type="datetime-local" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div class="md:col-span-2"><label class="text-sm font-medium">Вложения</label><input wire:model="attachments" type="file" multiple class="mt-1 block">@error('attachments.*')<p class="text-xs text-rose-600">{{ $message }}</p>@enderror</div>
</div>
<div class="flex justify-end gap-2">
<a href="{{ route('requests.index') }}" class="rounded-xl border border-slate-300 px-4 py-2">Отмена</a>
<button type="submit" class="rounded-xl bg-slate-900 text-white px-4 py-2">Сохранить</button>
</div>
</form>
</div></div>
@@ -0,0 +1,51 @@
<div class="space-y-4">
<div class="card"><div class="card-body">
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
<input wire:model.live.debounce.400ms="search" type="text" placeholder="Поиск по тикету/заголовку" class="rounded-xl border border-slate-300 px-3 py-2">
<select wire:model.live="statusId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все статусы</option>@foreach($statuses as $s)<option value="{{ $s->id }}">{{ $s->name }}</option>@endforeach</select>
<select wire:model.live="priorityId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все приоритеты</option>@foreach($priorities as $p)<option value="{{ $p->id }}">{{ $p->name }}</option>@endforeach</select>
<select wire:model.live="typeId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все типы</option>@foreach($types as $t)<option value="{{ $t->id }}">{{ $t->name }}</option>@endforeach</select>
<select wire:model.live="technicianId" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все техники</option>@foreach($technicians as $t)<option value="{{ $t->id }}">{{ $t->name }}</option>@endforeach</select>
<button wire:click="clearFilters" class="rounded-xl border border-slate-300 px-3 py-2 hover:bg-slate-50">Сброс</button>
<button wire:click="exportCsv" class="rounded-xl border border-slate-300 px-3 py-2 text-sm hover:bg-slate-50">Экспорт CSV</button>
</div>
</div></div>
<div class="card overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="bg-slate-50 text-slate-600">
<tr>
<th class="px-3 py-2"><input type="checkbox" wire:click="$set('selected', {{ json_encode($requests->pluck('id')->toArray()) }})"></th>
<th class="px-3 py-2 text-left cursor-pointer" wire:click="sortBy('ticket_number')">#Ticket</th>
<th class="px-3 py-2 text-left cursor-pointer" wire:click="sortBy('title')">Заголовок</th>
<th class="px-3 py-2 text-left">Клиент</th>
<th class="px-3 py-2 text-left">Исполнитель</th>
<th class="px-3 py-2 text-left">Приоритет</th>
<th class="px-3 py-2 text-left">Статус</th>
<th class="px-3 py-2 text-left">Дедлайн</th>
<th class="px-3 py-2 text-left">Создана</th>
</tr>
</thead>
<tbody>
@forelse($requests as $request)
<tr class="border-t border-slate-100 {{ $request->isOverdue() ? 'bg-rose-50' : '' }}">
<td class="px-3 py-2"><input type="checkbox" wire:model="selected" value="{{ $request->id }}"></td>
<td class="px-3 py-2"><a class="font-medium text-slate-900 hover:underline" href="{{ route('requests.show', $request) }}">{{ $request->ticket_number }}</a></td>
<td class="px-3 py-2">{{ $request->title }}</td>
<td class="px-3 py-2">{{ $request->client?->name }}</td>
<td class="px-3 py-2">{{ $request->assignee?->name ?? '—' }}</td>
<td class="px-3 py-2"><span class="inline-flex items-center rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->priority?->color_hex }}">{{ $request->priority?->name }}</span></td>
<td class="px-3 py-2"><span class="inline-flex items-center rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->status?->color_hex }}">{{ $request->status?->name }}</span></td>
<td class="px-3 py-2 {{ $request->isOverdue() ? 'text-rose-600 font-medium' : '' }}">{{ $request->deadline_at?->format('d.m.Y H:i') ?? '—' }}</td>
<td class="px-3 py-2">{{ $request->created_at?->format('d.m.Y H:i') }}</td>
</tr>
@empty
<tr><td colspan="9" class="px-3 py-8 text-center text-slate-500">Нет заявок.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="p-4">{{ $requests->links() }}</div>
</div>
</div>
@@ -0,0 +1,121 @@
<div class="space-y-4">
<div class="card"><div class="card-body">
<div class="flex flex-wrap justify-between gap-3">
<div>
<h1 class="text-xl font-semibold">{{ $request->ticket_number }} {{ $request->title }}</h1>
<p class="text-sm text-slate-500 mt-1">Создана {{ $request->created_at?->format('d.m.Y H:i') }}</p>
</div>
@if(auth()->user()->isAdmin() || auth()->user()->isManager())
<div class="flex gap-2">
<a href="{{ route('requests.edit', $request) }}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">Редактировать</a>
</div>
@endif
</div>
<div class="grid gap-4 md:grid-cols-2 mt-4">
<div class="rounded-xl border border-slate-200 p-3 space-y-2">
<p><span class="text-slate-500">Клиент:</span> {{ $request->client?->name }}</p>
@if(!auth()->user()->isClient())
<p><span class="text-slate-500">Исполнитель:</span> {{ $request->assignee?->name ?? '—' }}</p>
@endif
<p><span class="text-slate-500">Тип:</span> {{ $request->type?->name }}</p>
<p><span class="text-slate-500">Приоритет:</span> <span class="inline-flex rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->priority?->color_hex }}">{{ $request->priority?->name }}</span></p>
<p><span class="text-slate-500">Статус:</span> <span class="inline-flex rounded-full px-2 py-1 text-xs text-white" style="background-color: {{ $request->status?->color_hex }}">{{ $request->status?->name }}</span></p>
<p><span class="text-slate-500">Дедлайн:</span> {{ $request->deadline_at?->format('d.m.Y H:i') ?? '—' }}</p>
</div>
<div class="rounded-xl border border-slate-200 p-3">
<p class="text-sm text-slate-500 mb-1">Описание</p>
<p class="text-sm leading-6 whitespace-pre-wrap">{{ $request->description }}</p>
</div>
</div>
@if(!$allowedStatuses->isEmpty())
<div class="mt-4 flex items-center gap-2">
<select wire:model="nextStatusId" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><option value="">Сменить статус</option>@foreach($allowedStatuses as $status)<option value="{{ $status->id }}">{{ $status->name }}</option>@endforeach</select>
<button wire:click="changeStatus" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Применить</button>
</div>
@endif
</div></div>
<div class="grid gap-4 lg:grid-cols-3">
<div class="lg:col-span-2 space-y-4">
<div class="card"><div class="card-body">
<h2 class="text-lg font-semibold">Комментарии</h2>
<div class="mt-3 space-y-2">
@forelse($comments as $comment)
<div class="rounded-xl border border-slate-200 p-3">
<div class="flex items-center justify-between">
<p class="text-sm font-medium">{{ $comment->user?->name }}</p>
<p class="text-xs text-slate-500">{{ $comment->created_at?->format('d.m.Y H:i') }}</p>
</div>
@if($comment->is_internal)<p class="text-[11px] text-indigo-600 font-semibold uppercase mt-1">Внутренняя заметка</p>@endif
<p class="text-sm mt-2 whitespace-pre-wrap">{{ $comment->body }}</p>
@if($comment->user_id === auth()->id())
<button wire:click="deleteComment({{ $comment->id }})" class="mt-2 text-xs text-rose-600">Удалить</button>
@endif
</div>
@empty
<p class="text-sm text-slate-500">Комментариев пока нет.</p>
@endforelse
</div>
<div class="mt-4 space-y-2">
<textarea wire:model.defer="commentBody" rows="3" class="w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Добавить комментарий"></textarea>
@if(!auth()->user()->isClient())
<label class="inline-flex items-center gap-2 text-sm"><input wire:model="commentInternal" type="checkbox" class="rounded"> Внутренняя заметка</label>
@endif
<button wire:click="addComment" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Отправить</button>
</div>
</div></div>
<div class="card"><div class="card-body">
<h2 class="text-lg font-semibold">История активности</h2>
<div class="mt-3 space-y-2">
@forelse($activity as $log)
<div class="rounded-lg border border-slate-200 p-2 text-sm">
<span class="font-medium">{{ $log->user?->name ?? 'Система' }}</span> {{ $log->description }}
<span class="text-xs text-slate-500">({{ $log->created_at?->format('d.m.Y H:i') }})</span>
</div>
@empty
<p class="text-sm text-slate-500">История пуста.</p>
@endforelse
</div>
</div></div>
</div>
<div class="space-y-4">
<div class="card"><div class="card-body">
<h2 class="text-lg font-semibold">Вложения</h2>
<div class="mt-3 space-y-2 text-sm">
@forelse($request->attachments as $file)
<a href="{{ route('attachments.download', ['request' => $request->id, 'attachment' => $file->id]) }}" class="block rounded-lg border border-slate-200 px-3 py-2 hover:bg-slate-50">{{ $file->filename }}</a>
@empty
<p class="text-slate-500">Вложений нет.</p>
@endforelse
</div>
</div></div>
@if($request->equipment)
<div class="card"><div class="card-body">
<h2 class="text-lg font-semibold">Оборудование</h2>
<p class="text-sm mt-2">{{ $request->equipment->name }}</p>
<p class="text-xs text-slate-500">{{ $request->equipment->serial_number }}</p>
</div></div>
@endif
@if(auth()->user()->isClient() && in_array($request->status?->slug, ['resolved','closed'], true))
<div class="card"><div class="card-body">
<h2 class="text-lg font-semibold">Оценка заявки</h2>
<div class="mt-2 space-y-2">
<select wire:model="rating" class="w-full rounded-xl border border-slate-300 px-3 py-2">
<option value="">Выберите оценку</option>
@for($i = 1; $i <= 5; $i++) <option value="{{ $i }}">{{ $i }} </option> @endfor
</select>
<textarea wire:model="ratingComment" rows="3" class="w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Комментарий"></textarea>
<button wire:click="submitRating" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Сохранить оценку</button>
</div>
</div></div>
@endif
</div>
</div>
</div>
@@ -0,0 +1,10 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex flex-wrap gap-2 items-center">
<select wire:model.live="userId" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><option value="">Все пользователи</option>@foreach($users as $user)<option value="{{ $user->id }}">{{ $user->name }}</option>@endforeach</select>
<input wire:model.live.debounce.300ms="action" class="rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Тип действия">
<input wire:model.live="dateFrom" type="date" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<input wire:model.live="dateTo" type="date" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">
<button wire:click="exportCsv" class="rounded-xl bg-slate-900 text-white px-3 py-2 text-sm">Экспорт CSV</button>
</div></div>
<div class="card overflow-hidden"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Пользователь</th><th class="px-3 py-2 text-left">Действие</th><th class="px-3 py-2 text-left">Сущность</th><th class="px-3 py-2 text-left">Описание</th><th class="px-3 py-2 text-left">IP</th><th class="px-3 py-2 text-left">Дата</th></tr></thead><tbody>@forelse($logs as $log)<tr class="border-t border-slate-100"><td class="px-3 py-2">{{ $log->user?->name ?? 'Система' }}</td><td class="px-3 py-2">{{ $log->action }}</td><td class="px-3 py-2">{{ class_basename($log->model_type) }}</td><td class="px-3 py-2">{{ $log->description }}</td><td class="px-3 py-2">{{ $log->ip_address }}</td><td class="px-3 py-2">{{ $log->created_at?->format('d.m.Y H:i:s') }}</td></tr>@empty<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">Нет записей</td></tr>@endforelse</tbody></table><div class="p-4">{{ $logs->links() }}</div></div>
</div>
@@ -0,0 +1,23 @@
<div class="card max-w-4xl"><div class="card-body">
<form wire:submit="save" class="space-y-4">
<div class="grid gap-4 md:grid-cols-2">
<div><label class="text-sm font-medium">Название компании</label><input wire:model.defer="companyName" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Часовой пояс</label><select wire:model.defer="timezone" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">@foreach($russianTimezones as $value => $label)<option value="{{ $value }}">{{ $label }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">SLA по умолчанию (часы)</label><input wire:model.defer="defaultSlaHours" type="number" min="1" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div class="md:col-span-2"><label class="inline-flex items-center gap-2"><input wire:model.defer="maintenanceMode" type="checkbox" class="rounded"> <span class="text-sm">Режим обслуживания</span></label></div>
</div>
<div class="rounded-xl border border-slate-200 p-4">
<h3 class="font-semibold mb-3">Email-уведомления</h3>
<div class="grid gap-2 md:grid-cols-2 text-sm">
<label class="inline-flex items-center gap-2"><input wire:model.defer="notifyAssigned" type="checkbox" class="rounded"> Назначение заявки</label>
<label class="inline-flex items-center gap-2"><input wire:model.defer="notifyStatusChanged" type="checkbox" class="rounded"> Изменение статуса</label>
<label class="inline-flex items-center gap-2"><input wire:model.defer="notifyCommentAdded" type="checkbox" class="rounded"> Новый комментарий</label>
<label class="inline-flex items-center gap-2"><input wire:model.defer="notifyOverdue" type="checkbox" class="rounded"> Просрочка</label>
<label class="inline-flex items-center gap-2"><input wire:model.defer="notifySlaWarning" type="checkbox" class="rounded"> Предупреждение SLA</label>
</div>
</div>
<div class="flex justify-end"><button class="rounded-xl bg-slate-900 text-white px-4 py-2">Сохранить настройки</button></div>
</form>
</div></div>
@@ -0,0 +1,36 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex flex-wrap gap-2">
<select wire:model.live="priorityId" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><option value="">Все приоритеты</option>@foreach($priorities as $p)<option value="{{ $p->id }}">{{ $p->name }}</option>@endforeach</select>
<select wire:model.live="deadlineFilter" class="rounded-xl border border-slate-300 px-3 py-2 text-sm"><option value="">Все дедлайны</option><option value="today">Сегодня</option><option value="overdue">Просроченные</option></select>
</div></div>
<div class="grid gap-4 lg:grid-cols-4">
@foreach($statuses as $status)
<div class="card">
<div class="card-body">
<h3 class="font-semibold text-sm mb-3" style="color: {{ $status->color_hex }}">{{ $status->name }}</h3>
<div class="space-y-3">
@foreach(($requestsByStatus[$status->slug] ?? collect()) as $task)
<div class="rounded-xl border border-slate-200 p-3">
<a href="{{ route('requests.show', $task) }}" class="font-medium text-sm hover:underline">{{ $task->ticket_number }} {{ $task->title }}</a>
<p class="text-xs text-slate-500 mt-1">{{ $task->client?->name }} {{ $task->equipment?->name ?? 'Без оборудования' }}</p>
<p class="text-xs mt-1 {{ $task->isOverdue() ? 'text-rose-600 font-semibold' : 'text-slate-500' }}">{{ $task->deadline_at?->diffForHumans() ?? 'Без дедлайна' }}</p>
<div class="mt-2 flex gap-1 flex-wrap">
@foreach($this->transitionOptions($task) as $transition)
<button wire:click="updateStatus({{ $task->id }}, '{{ $transition->id }}')" class="text-[10px] rounded border border-slate-300 px-1.5 py-1">{{ $transition->name }}</button>
@endforeach
</div>
<div class="mt-2">
<input wire:model.defer="quickComments.{{ $task->id }}" type="text" class="w-full rounded-lg border border-slate-300 px-2 py-1 text-xs" placeholder="Внутренняя заметка">
<button wire:click="addQuickComment({{ $task->id }})" class="mt-1 text-xs rounded bg-slate-900 text-white px-2 py-1">Добавить</button>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endforeach
</div>
</div>
@@ -0,0 +1,15 @@
<div class="card"><div class="card-body">
<form wire:submit="save" class="space-y-4">
<div class="grid gap-4 md:grid-cols-2">
<div><label class="text-sm font-medium">Имя</label><input wire:model.defer="name" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Email</label><input wire:model.defer="email" type="email" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Роль</label><select wire:model.defer="role" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="admin">Админ</option><option value="manager">Менеджер</option><option value="technician">Техник</option><option value="client">Клиент</option></select></div>
<div><label class="text-sm font-medium">Отдел</label><select wire:model.defer="departmentId" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Не выбран</option>@foreach($departments as $item)<option value="{{ $item->id }}">{{ $item->name }}</option>@endforeach</select></div>
<div><label class="text-sm font-medium">Телефон</label><input wire:model.defer="phone" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Пароль</label><input wire:model.defer="password" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div><label class="text-sm font-medium">Подтверждение пароля</label><input wire:model.defer="password_confirmation" type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></div>
<div class="md:col-span-2"><label class="inline-flex items-center gap-2"><input wire:model.defer="isActive" type="checkbox" class="rounded"> <span class="text-sm">Активен</span></label></div>
</div>
<div class="flex justify-end gap-2"><a href="{{ route('users.index') }}" class="rounded-xl border border-slate-300 px-4 py-2">Отмена</a><button class="rounded-xl bg-slate-900 text-white px-4 py-2">Сохранить</button></div>
</form>
</div></div>
@@ -0,0 +1,23 @@
<div class="space-y-4">
<div class="card"><div class="card-body flex items-start justify-between gap-3">
<div>
<h1 class="text-xl font-semibold">{{ $user->name }}</h1>
<p class="text-sm text-slate-500">{{ $user->email }} {{ $user->role->label() }}</p>
<p class="text-sm text-slate-500 mt-1">{{ $user->department?->name ?? 'Без отдела' }}</p>
</div>
@if(auth()->user()->isAdmin())
<a href="{{ route('users.edit', $user) }}" class="rounded-xl border border-slate-300 px-3 py-2 text-sm">Редактировать</a>
@endif
</div></div>
<div class="grid gap-4 md:grid-cols-3">
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Назначено заявок</p><p class="text-2xl font-semibold mt-1">{{ $assigned }}</p></div></div>
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Решено заявок</p><p class="text-2xl font-semibold mt-1">{{ $resolved }}</p></div></div>
<div class="card"><div class="card-body"><p class="text-sm text-slate-500">Средний рейтинг</p><p class="text-2xl font-semibold mt-1">{{ $avgRating }}</p></div></div>
</div>
<div class="grid gap-4 lg:grid-cols-2">
<div class="card"><div class="card-body"><h2 class="text-lg font-semibold">Последние заявки</h2><div class="space-y-2 mt-3">@foreach($recentRequests as $item)<a href="{{ route('requests.show', $item) }}" class="block rounded-lg border border-slate-200 px-3 py-2 hover:bg-slate-50 text-sm">{{ $item->ticket_number }} {{ $item->title }}</a>@endforeach</div></div></div>
<div class="card"><div class="card-body"><h2 class="text-lg font-semibold">Активность</h2><div class="space-y-2 mt-3">@foreach($activities as $item)<div class="rounded-lg border border-slate-200 px-3 py-2 text-sm">{{ $item->description }}<p class="text-xs text-slate-500 mt-1">{{ $item->created_at?->format('d.m.Y H:i') }}</p></div>@endforeach</div></div></div>
</div>
</div>
@@ -0,0 +1,33 @@
<div class="space-y-4">
<div class="flex flex-wrap items-center gap-2 justify-between">
<div class="flex flex-wrap gap-2">
<input wire:model.live.debounce.300ms="search" class="rounded-xl border border-slate-300 px-3 py-2" placeholder="Поиск пользователей">
<select wire:model.live="role" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все роли</option><option value="admin">Админ</option><option value="manager">Менеджер</option><option value="technician">Техник</option><option value="client">Клиент</option></select>
<select wire:model.live="status" class="rounded-xl border border-slate-300 px-3 py-2"><option value="">Все статусы</option><option value="active">Активные</option><option value="inactive">Неактивные</option></select>
</div>
@if(auth()->user()->isAdmin())
<a href="{{ route('users.create') }}" class="rounded-xl bg-slate-900 text-white px-4 py-2 text-sm">Создать пользователя</a>
@endif
</div>
<div class="card overflow-hidden">
<table class="min-w-full text-sm">
<thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Пользователь</th><th class="px-3 py-2 text-left">Роль</th><th class="px-3 py-2 text-left">Отдел</th><th class="px-3 py-2 text-left">Статус</th><th class="px-3 py-2 text-left">Последний вход</th><th class="px-3 py-2"></th></tr></thead>
<tbody>
@forelse($users as $user)
<tr class="border-t border-slate-100">
<td class="px-3 py-2"><p class="font-medium">{{ $user->name }}</p><p class="text-xs text-slate-500">{{ $user->email }}</p></td>
<td class="px-3 py-2">{{ $user->role->label() }}</td>
<td class="px-3 py-2">{{ $user->department?->name ?? '—' }}</td>
<td class="px-3 py-2">{!! $user->is_active ? '<span class="text-emerald-600">Активен</span>' : '<span class="text-rose-600">Неактивен</span>' !!}</td>
<td class="px-3 py-2">{{ $user->last_login_at?->format('d.m.Y H:i') ?? '—' }}</td>
<td class="px-3 py-2 text-right"><a href="{{ route('users.show', $user) }}" class="text-sm text-slate-700 hover:underline">Открыть</a></td>
</tr>
@empty
<tr><td colspan="6" class="px-3 py-8 text-center text-slate-500">Нет пользователей</td></tr>
@endforelse
</tbody>
</table>
<div class="p-4">{{ $users->links() }}</div>
</div>
</div>
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
@fluxAppearance
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="antialiased">
@livewireScripts
@fluxScripts
</body>
</html>