index.blade.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. @extends('layouts.app')
  2. @section('content')
  3. <div class="row mb-3 page-header-row">
  4. <div class="col-12 col-md-6 page-header-title">
  5. <h3>Документация</h3>
  6. </div>
  7. @if($canCreateRoot)
  8. <div class="col-12 col-md-6 text-md-end page-header-actions">
  9. <button class="btn btn-sm btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#createRootFolder">
  10. <i class="bi bi-folder-plus"></i> Корневая папка
  11. </button>
  12. </div>
  13. @endif
  14. </div>
  15. @if($canCreateRoot)
  16. <div class="collapse mb-3" id="createRootFolder">
  17. <div class="card card-body">
  18. <form action="{{ route('documents.folders.store') }}" method="POST" class="row g-2 align-items-end">
  19. @csrf
  20. <div class="col-md-8">
  21. <label class="form-label" for="root-folder-name">Название корневой папки</label>
  22. <input class="form-control" id="root-folder-name" name="name" maxlength="255" required>
  23. </div>
  24. <div class="col-md-4">
  25. <button class="btn btn-primary w-100" type="submit">Создать</button>
  26. </div>
  27. </form>
  28. </div>
  29. </div>
  30. @endif
  31. <div class="row g-3">
  32. <div class="col-12 col-lg-3">
  33. <div class="card h-100">
  34. <div class="card-header fw-semibold">Папки</div>
  35. <div class="card-body p-2">
  36. @include('documents._folder_tree', [
  37. 'parentId' => 0,
  38. 'foldersByParent' => $foldersByParent,
  39. 'selectedFolder' => $selectedFolder,
  40. ])
  41. @if($folders->isEmpty())
  42. <div class="text-muted small p-2">
  43. {{ $canCreateRoot ? 'Создайте первую корневую папку.' : 'Доступных папок пока нет.' }}
  44. </div>
  45. @endif
  46. </div>
  47. </div>
  48. </div>
  49. <div class="col-12 col-lg-9">
  50. @if($selectedFolder)
  51. <div class="d-flex flex-wrap justify-content-between align-items-start gap-2 mb-3">
  52. <div>
  53. <h4 class="mb-1"><i class="bi bi-folder2-open"></i> {{ $selectedFolder->name }}</h4>
  54. <div class="small text-muted">
  55. @if($selectedFolder->parent_id && $selectedFolder->inherits_permissions)
  56. Права наследуются от родительской папки
  57. @else
  58. Для папки настроены собственные права
  59. @endif
  60. </div>
  61. </div>
  62. @if($canWrite)
  63. <div class="d-flex flex-wrap gap-1">
  64. <button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#folderSettings">
  65. <i class="bi bi-pencil"></i> Папка
  66. </button>
  67. <button class="btn btn-sm btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#uploadDocument">
  68. <i class="bi bi-file-earmark-arrow-up"></i> Загрузить документ
  69. </button>
  70. </div>
  71. @endif
  72. </div>
  73. @if($canWrite)
  74. <div class="collapse mb-3" id="folderSettings">
  75. <div class="card card-body">
  76. <div class="row g-3">
  77. <div class="col-md-6">
  78. <h6>Переименовать папку</h6>
  79. <form action="{{ route('documents.folders.update', $selectedFolder) }}" method="POST" class="d-flex gap-2">
  80. @csrf
  81. @method('PUT')
  82. <input class="form-control" name="name" value="{{ $selectedFolder->name }}" maxlength="255" required>
  83. <button class="btn btn-outline-primary" type="submit">Сохранить</button>
  84. </form>
  85. </div>
  86. <div class="col-md-6">
  87. <h6>Создать подпапку</h6>
  88. <form action="{{ route('documents.folders.store') }}" method="POST" class="d-flex gap-2">
  89. @csrf
  90. <input type="hidden" name="parent_id" value="{{ $selectedFolder->id }}">
  91. <input class="form-control" name="name" placeholder="Название подпапки" maxlength="255" required>
  92. <button class="btn btn-outline-primary" type="submit">Создать</button>
  93. </form>
  94. </div>
  95. </div>
  96. <hr>
  97. <form action="{{ route('documents.folders.destroy', $selectedFolder) }}" method="POST"
  98. onsubmit="return confirm('Удалить папку, все вложенные папки, документы и версии?')">
  99. @csrf
  100. @method('DELETE')
  101. <button class="btn btn-sm btn-outline-danger" type="submit">
  102. <i class="bi bi-trash"></i> Удалить папку со всем содержимым
  103. </button>
  104. </form>
  105. </div>
  106. </div>
  107. <div class="collapse mb-3" id="uploadDocument">
  108. <div class="card card-body">
  109. <h6>Новый документ</h6>
  110. <form action="{{ route('documents.store', $selectedFolder) }}" method="POST" enctype="multipart/form-data" class="row g-2 align-items-end">
  111. @csrf
  112. <div class="col-md-5">
  113. <label class="form-label" for="document-name">Название</label>
  114. <input class="form-control" id="document-name" name="name" maxlength="255" placeholder="По умолчанию — имя файла">
  115. </div>
  116. <div class="col-md-5">
  117. <label class="form-label" for="document-file">Файл, до 20 МБ</label>
  118. <input class="form-control" id="document-file" type="file" name="file" required>
  119. </div>
  120. <div class="col-md-2">
  121. <button class="btn btn-primary w-100" type="submit">Загрузить</button>
  122. </div>
  123. </form>
  124. </div>
  125. </div>
  126. @endif
  127. @if($canManagePermissions)
  128. <div class="card mb-3">
  129. <div class="card-header p-0">
  130. <button class="btn w-100 text-start px-3 py-2" type="button" data-bs-toggle="collapse" data-bs-target="#folderPermissions">
  131. <i class="bi bi-shield-lock"></i> Права доступа
  132. </button>
  133. </div>
  134. <div class="collapse" id="folderPermissions">
  135. <div class="card-body">
  136. <form action="{{ route('documents.folders.permissions.update', $selectedFolder) }}" method="POST">
  137. @csrf
  138. @method('PUT')
  139. @if($selectedFolder->parent_id)
  140. <div class="form-check mb-3">
  141. <input class="form-check-input" type="checkbox" value="1" name="inherit_permissions" id="inherit-permissions"
  142. @checked($selectedFolder->inherits_permissions)>
  143. <label class="form-check-label" for="inherit-permissions">
  144. Наследовать права родительской папки
  145. </label>
  146. </div>
  147. @endif
  148. <div class="row g-3">
  149. <div class="col-12">
  150. <div class="d-flex gap-4">
  151. <div class="form-check">
  152. <input class="form-check-input" type="checkbox" value="1" name="all_read" id="all-read"
  153. @checked($permissionValues['all_read'])>
  154. <label class="form-check-label" for="all-read">Все: чтение</label>
  155. </div>
  156. <div class="form-check">
  157. <input class="form-check-input" type="checkbox" value="1" name="all_write" id="all-write"
  158. @checked($permissionValues['all_write'])>
  159. <label class="form-check-label" for="all-write">Все: запись</label>
  160. </div>
  161. </div>
  162. </div>
  163. <div class="col-md-6">
  164. <label class="form-label" for="role-read">Роли: чтение</label>
  165. <select class="form-select" id="role-read" name="role_read_ids[]" multiple size="5">
  166. @foreach($roles as $role)
  167. <option value="{{ $role->id }}" @selected(in_array($role->id, $permissionValues['role_read_ids'], true))>{{ $role->name }}</option>
  168. @endforeach
  169. </select>
  170. </div>
  171. <div class="col-md-6">
  172. <label class="form-label" for="role-write">Роли: запись</label>
  173. <select class="form-select" id="role-write" name="role_write_ids[]" multiple size="5">
  174. @foreach($roles as $role)
  175. <option value="{{ $role->id }}" @selected(in_array($role->id, $permissionValues['role_write_ids'], true))>{{ $role->name }}</option>
  176. @endforeach
  177. </select>
  178. </div>
  179. <div class="col-md-6">
  180. <label class="form-label" for="user-read">Пользователи: чтение</label>
  181. <select class="form-select" id="user-read" name="user_read_ids[]" multiple size="7">
  182. @foreach($users as $user)
  183. <option value="{{ $user->id }}" @selected(in_array($user->id, $permissionValues['user_read_ids'], true))>
  184. {{ $user->name }} ({{ $user->email }})
  185. </option>
  186. @endforeach
  187. </select>
  188. </div>
  189. <div class="col-md-6">
  190. <label class="form-label" for="user-write">Пользователи: запись</label>
  191. <select class="form-select" id="user-write" name="user_write_ids[]" multiple size="7">
  192. @foreach($users as $user)
  193. <option value="{{ $user->id }}" @selected(in_array($user->id, $permissionValues['user_write_ids'], true))>
  194. {{ $user->name }} ({{ $user->email }})
  195. </option>
  196. @endforeach
  197. </select>
  198. </div>
  199. </div>
  200. <p class="small text-muted mt-3 mb-2">Право записи автоматически включает чтение. При включённом наследовании собственные правила удаляются.</p>
  201. <button class="btn btn-primary" type="submit">Сохранить права</button>
  202. </form>
  203. </div>
  204. </div>
  205. </div>
  206. @endif
  207. <form action="{{ route('documents.index') }}" method="GET" class="row g-2 mb-3">
  208. <input type="hidden" name="folder" value="{{ $selectedFolder->id }}">
  209. <div class="col-md-10">
  210. <input class="form-control" name="search" value="{{ $search }}" placeholder="Поиск документа по названию">
  211. </div>
  212. <div class="col-md-2">
  213. <button class="btn btn-outline-primary w-100" type="submit"><i class="bi bi-search"></i> Найти</button>
  214. </div>
  215. </form>
  216. <div class="card">
  217. <div class="table-responsive">
  218. <table class="table table-hover align-middle mb-0">
  219. <thead>
  220. <tr>
  221. <th>Документ</th>
  222. <th>Текущая версия</th>
  223. <th>Обновлён</th>
  224. <th>Автор</th>
  225. <th class="text-end">Действия</th>
  226. </tr>
  227. </thead>
  228. <tbody>
  229. @forelse($documents as $document)
  230. @php($currentVersion = $document->currentVersion)
  231. <tr>
  232. <td class="text-break">
  233. @if($currentVersion?->isImage())
  234. <a href="{{ route('documents.versions.preview', $currentVersion) }}" target="_blank" rel="noopener"
  235. class="text-body text-decoration-none" title="Открыть изображение">
  236. <i class="{{ $currentVersion->iconClass() }} me-1"></i>{{ $document->name }}
  237. </a>
  238. @else
  239. <i class="{{ $currentVersion?->iconClass() ?? 'bi bi-file-earmark' }} me-1"></i>{{ $document->name }}
  240. @endif
  241. <details class="mt-2">
  242. <summary class="small text-primary" style="cursor: pointer">Все версии ({{ $document->versions->count() }})</summary>
  243. <div class="mt-2">
  244. @foreach($document->versions as $version)
  245. <div class="d-flex flex-wrap align-items-center gap-2 border-top py-2 small">
  246. <span class="badge text-bg-secondary">v{{ $version->version }}</span>
  247. @if($version->isImage())
  248. <a href="{{ route('documents.versions.preview', $version) }}" target="_blank" rel="noopener"
  249. class="text-body text-decoration-none" title="Открыть изображение">
  250. <i class="{{ $version->iconClass() }}"></i> {{ $version->file?->original_name }}
  251. </a>
  252. @else
  253. <span><i class="{{ $version->iconClass() }}"></i> {{ $version->file?->original_name }}</span>
  254. @endif
  255. <span class="text-muted">{{ number_format($version->file_size / 1024, 1, ',', ' ') }} КБ</span>
  256. <span class="text-muted">{{ $version->created_at?->format('d.m.Y H:i') }}</span>
  257. <a class="btn btn-sm btn-outline-primary" href="{{ route('documents.versions.download', $version) }}">Скачать</a>
  258. @if($canWrite)
  259. <form action="{{ route('documents.versions.destroy', $version) }}" method="POST"
  260. onsubmit="return confirm('Полностью удалить эту версию?')">
  261. @csrf
  262. @method('DELETE')
  263. <button class="btn btn-sm btn-outline-danger" type="submit">Удалить</button>
  264. </form>
  265. @endif
  266. </div>
  267. @endforeach
  268. </div>
  269. </details>
  270. </td>
  271. <td>v{{ $currentVersion?->version }}</td>
  272. <td>{{ $currentVersion?->created_at?->format('d.m.Y H:i') }}</td>
  273. <td>{{ $currentVersion?->creator?->name }}</td>
  274. <td class="text-end">
  275. @if($currentVersion)
  276. <a class="btn btn-sm btn-outline-primary mb-1" href="{{ route('documents.versions.download', $currentVersion) }}">
  277. <i class="bi bi-download"></i>
  278. </a>
  279. @endif
  280. @if($canWrite)
  281. <button class="btn btn-sm btn-outline-secondary mb-1" type="button" data-bs-toggle="collapse"
  282. data-bs-target="#document-actions-{{ $document->id }}">
  283. <i class="bi bi-pencil"></i>
  284. </button>
  285. @endif
  286. </td>
  287. </tr>
  288. @if($canWrite)
  289. <tr class="collapse" id="document-actions-{{ $document->id }}">
  290. <td colspan="5" class="bg-light">
  291. <div class="row g-3">
  292. <div class="col-lg-6">
  293. <form action="{{ route('documents.update', $document) }}" method="POST" class="d-flex gap-2">
  294. @csrf
  295. @method('PUT')
  296. <input class="form-control form-control-sm" name="name" value="{{ $document->name }}" maxlength="255" required>
  297. <button class="btn btn-sm btn-outline-primary" type="submit">Переименовать</button>
  298. </form>
  299. </div>
  300. <div class="col-lg-6">
  301. <form action="{{ route('documents.versions.store', $document) }}" method="POST" enctype="multipart/form-data" class="d-flex gap-2">
  302. @csrf
  303. <input class="form-control form-control-sm" type="file" name="file" required>
  304. <button class="btn btn-sm btn-outline-primary" type="submit">Новая версия</button>
  305. </form>
  306. </div>
  307. <div class="col-12">
  308. <form action="{{ route('documents.destroy', $document) }}" method="POST"
  309. onsubmit="return confirm('Полностью удалить документ и все версии?')">
  310. @csrf
  311. @method('DELETE')
  312. <button class="btn btn-sm btn-outline-danger" type="submit">Удалить документ</button>
  313. </form>
  314. </div>
  315. </div>
  316. </td>
  317. </tr>
  318. @endif
  319. @empty
  320. <tr>
  321. <td colspan="5" class="text-center text-muted py-4">
  322. {{ $search !== '' ? 'Документы по запросу не найдены.' : 'В папке пока нет документов.' }}
  323. </td>
  324. </tr>
  325. @endforelse
  326. </tbody>
  327. </table>
  328. </div>
  329. </div>
  330. @if($documents->hasPages())
  331. <div class="mt-3">
  332. {{ $documents->links() }}
  333. </div>
  334. @endif
  335. @else
  336. <div class="card card-body text-center text-muted py-5">
  337. <i class="bi bi-folder2-open fs-1"></i>
  338. <p class="mb-0 mt-2">Выберите доступную папку или создайте новую.</p>
  339. </div>
  340. @endif
  341. </div>
  342. </div>
  343. @endsection