feat: patient view, media player, and rich editor enhancements
This commit is contained in:
@@ -210,10 +210,36 @@ [dir="rtl"] .prose p {
|
||||
}
|
||||
|
||||
.fi-photo-gallery .filepond--root {
|
||||
contain: layout style !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-height: 60px !important;
|
||||
height: auto !important;
|
||||
margin-top: 8px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--list-scroller {
|
||||
order: 2 !important;
|
||||
position: relative !important;
|
||||
transform: none !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
top: unset !important;
|
||||
left: unset !important;
|
||||
right: unset !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--drop-label {
|
||||
order: 1 !important;
|
||||
position: relative !important;
|
||||
top: unset !important;
|
||||
left: unset !important;
|
||||
right: unset !important;
|
||||
bottom: unset !important;
|
||||
transform: none !important;
|
||||
margin-top: 4px !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.fi-photo-gallery-view .filepond--root,
|
||||
[data-hide-dropzone] .filepond--root {
|
||||
@@ -240,6 +266,12 @@ .fi-photo-gallery .filepond--item {
|
||||
overflow: hidden !important;
|
||||
border-radius: 6px !important;
|
||||
flex-shrink: 0 !important;
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--item * {
|
||||
transition: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--image-preview-wrapper {
|
||||
position: absolute !important;
|
||||
@@ -249,16 +281,18 @@ .fi-photo-gallery .filepond--image-preview-wrapper {
|
||||
height: 100% !important;
|
||||
max-height: none !important;
|
||||
margin-top: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--image-preview-overlay,
|
||||
.fi-photo-gallery .filepond--image-preview {
|
||||
height: 56px !important;
|
||||
max-height: 56px !important;
|
||||
height: 100% !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--image-preview canvas {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover !important;
|
||||
display: block !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--panel-root,
|
||||
.fi-photo-gallery .filepond--panel-top,
|
||||
@@ -279,15 +313,38 @@ .fi-photo-gallery .filepond--file-action-button {
|
||||
.fi-photo-gallery .filepond--file-info {
|
||||
display: none !important;
|
||||
}
|
||||
.fi-photo-gallery .filepond--drop-label {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
.fi-video-gallery .filepond--root {
|
||||
contain: layout style !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-height: 60px !important;
|
||||
height: auto !important;
|
||||
margin-top: 8px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
.fi-video-gallery .filepond--list-scroller {
|
||||
order: 2 !important;
|
||||
position: relative !important;
|
||||
transform: none !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
top: unset !important;
|
||||
left: unset !important;
|
||||
right: unset !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.fi-video-gallery .filepond--drop-label {
|
||||
order: 1 !important;
|
||||
position: relative !important;
|
||||
top: unset !important;
|
||||
left: unset !important;
|
||||
right: unset !important;
|
||||
bottom: unset !important;
|
||||
transform: none !important;
|
||||
margin-bottom: 4px !important;
|
||||
height: auto !important;
|
||||
}
|
||||
.fi-video-gallery .filepond--list {
|
||||
position: relative !important;
|
||||
transform: none !important;
|
||||
@@ -337,9 +394,6 @@ .fi-video-gallery .filepond--file-action-button {
|
||||
width: 1.4rem !important;
|
||||
height: 1.4rem !important;
|
||||
}
|
||||
.fi-video-gallery .filepond--drop-label {
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
|
||||
.fi-photo-gallery-view .filepond--file-action-button.filepond--action-remove-item,
|
||||
.fi-photo-gallery-view .filepond--file-action-button.filepond--action-revert-item-processing,
|
||||
@@ -398,7 +452,8 @@ #fi-lb-img.lb-zoomed.lb-dragging {
|
||||
#fi-lb-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
left: 1rem;
|
||||
right: auto;
|
||||
z-index: 10;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
@@ -465,3 +520,139 @@ #fi-lb-hint {
|
||||
pointer-events: none;
|
||||
direction: rtl;
|
||||
}
|
||||
#fi-lb-caption {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 280px;
|
||||
height: auto;
|
||||
max-height: calc(100% - 2rem);
|
||||
background: rgba(255, 255, 255, 0.97);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 14px;
|
||||
direction: rtl;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
|
||||
}
|
||||
#fi-lb-caption.lb-cap-hidden {
|
||||
transform: translateX(calc(100% + 1rem));
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
#fi-lb-cap-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 11px 13px 9px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
#fi-lb-cap-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 7px;
|
||||
background: rgba(59, 130, 246, 0.12);
|
||||
border: 1px solid rgba(59, 130, 246, 0.25);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#fi-lb-cap-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: rgba(10, 15, 35, 0.9);
|
||||
letter-spacing: 0.02em;
|
||||
flex: 1;
|
||||
}
|
||||
#fi-lb-cap-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid rgba(0, 0, 0, 0.18);
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
#fi-lb-cap-close:hover {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
#fi-lb-cap-body {
|
||||
padding: 14px 13px 18px;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.9;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
color: rgba(20, 30, 50, 0.88);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0,0,0,0.15) transparent;
|
||||
}
|
||||
#fi-lb-cap-body::-webkit-scrollbar { width: 3px; }
|
||||
#fi-lb-cap-body::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 2px; }
|
||||
#fi-lb-cap-body mark { border-radius: 2px; padding: 0 2px; }
|
||||
#fi-lb-cap-body ul, #fi-lb-cap-body ol { padding-right: 18px; margin: 4px 0; }
|
||||
#fi-lb-cap-body li { margin: 2px 0; }
|
||||
#fi-lb-cap-body strong { color: rgba(10, 20, 40, 0.95); }
|
||||
#fi-lb-cap-body blockquote { border-right: 3px solid rgba(59,130,246,0.4); padding-right: 10px; margin: 6px 0; color: rgba(20,30,50,0.6); font-style: normal; }
|
||||
#fi-lb-cap-toggle {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 10;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
background: rgba(40, 44, 60, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s, border-color 0.2s, opacity 0.2s;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
#fi-lb-cap-toggle.lb-cap-toggle-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
#fi-lb-cap-toggle:hover {
|
||||
background: rgba(60, 65, 90, 0.95);
|
||||
border-color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
#fi-lb-img {
|
||||
max-width: calc(100vw - 310px);
|
||||
transition: max-width 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.15s ease, transform 0.2s ease;
|
||||
}
|
||||
#fi-lightbox.lb-no-caption #fi-lb-img,
|
||||
#fi-lightbox.lb-cap-collapsed #fi-lb-img {
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
details.fi-text-expand > summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
details.fi-text-expand > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
details.fi-text-expand[open] > summary {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Mark } from '@tiptap/core'
|
||||
|
||||
export default function () {
|
||||
return Mark.create({
|
||||
name: 'highlightColor',
|
||||
|
||||
priority: 1001,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'span[data-hbg]',
|
||||
getAttrs: (el) => ({ 'data-hbg': el.getAttribute('data-hbg') }),
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
const color = HTMLAttributes['data-hbg']
|
||||
return [
|
||||
'span',
|
||||
{
|
||||
'data-hbg': color,
|
||||
style: `background-color: ${color};`,
|
||||
},
|
||||
0,
|
||||
]
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
'data-hbg': {
|
||||
default: null,
|
||||
parseHTML: (el) => el.getAttribute('data-hbg'),
|
||||
renderHTML: (attrs) => {
|
||||
if (!attrs['data-hbg']) return {}
|
||||
return { 'data-hbg': attrs['data-hbg'] }
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@php
|
||||
$files = $getState() ?? [];
|
||||
$files = is_array($files) ? array_values(array_filter($files)) : [];
|
||||
$urls = array_values(array_map(
|
||||
fn ($path) => \Illuminate\Support\Facades\Storage::disk('public')->url($path),
|
||||
$files
|
||||
));
|
||||
@endphp
|
||||
|
||||
@if(count($urls) > 0)
|
||||
<div class="flex flex-col gap-1.5">
|
||||
@foreach($urls as $idx => $url)
|
||||
@php
|
||||
$filename = basename(parse_url($url, PHP_URL_PATH));
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$isVideo = in_array($ext, ['mp4', 'mov', 'avi', 'mkv', 'webm']);
|
||||
@endphp
|
||||
<a
|
||||
href="{{ $url }}"
|
||||
target="_blank"
|
||||
download
|
||||
class="flex items-center gap-2 text-sm text-primary-600 hover:text-primary-800 transition-colors"
|
||||
>
|
||||
@if($isVideo)
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.069A1 1 0 0121 8.879V15.12a1 1 0 01-1.447.894L15 14M3 8a2 2 0 012-2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z"/>
|
||||
</svg>
|
||||
@else
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/>
|
||||
</svg>
|
||||
@endif
|
||||
<span class="truncate max-w-[180px]">{{ $idx + 1 }}. {{ $filename }}</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="text-sm text-gray-400">-</span>
|
||||
@endif
|
||||
@@ -0,0 +1 @@
|
||||
@livewire('patient-media-player', ['patientId' => $patientId, 'field' => $field], key('media-' . $patientId . '-' . $field))
|
||||
@@ -0,0 +1,33 @@
|
||||
@php
|
||||
$images = $getState() ?? [];
|
||||
$images = is_array($images) ? array_values(array_filter($images)) : [];
|
||||
$urls = array_values(array_map(
|
||||
fn ($path) => \Illuminate\Support\Facades\Storage::disk('public')->url($path),
|
||||
$images
|
||||
));
|
||||
$group = 'gallery-' . md5(implode(',', $urls));
|
||||
@endphp
|
||||
|
||||
@if(count($urls) > 0)
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
@foreach($urls as $idx => $url)
|
||||
<div
|
||||
class="relative cursor-pointer rounded-lg overflow-hidden border border-gray-200 hover:border-primary-400 hover:opacity-80 transition-all shadow-sm group"
|
||||
style="aspect-ratio:16/9;"
|
||||
data-lb-group="{{ $group }}"
|
||||
data-lb-idx="{{ $idx }}"
|
||||
data-lb-src="{{ $url }}"
|
||||
>
|
||||
<img
|
||||
src="{{ $url }}"
|
||||
class="w-full h-full object-cover"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition duration-200"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="text-sm text-gray-400">-</span>
|
||||
@endif
|
||||
@@ -0,0 +1 @@
|
||||
@livewire('expandable-text', ['html' => $html], key($key))
|
||||
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<div class="prose prose-sm max-w-none overflow-hidden transition-all duration-300"
|
||||
style="{{ $expanded ? '' : 'max-height: 8rem;' }}">
|
||||
{!! $html !!}
|
||||
</div>
|
||||
<button type="button" wire:click="toggle" class="text-primary-600 text-xs font-semibold hover:underline mt-1">
|
||||
{{ $expanded ? 'کمتر' : 'بیشتر' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
{{ $this->form }}
|
||||
</div>
|
||||
@@ -1,4 +1,18 @@
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="flex items-center gap-1"
|
||||
x-init="if (['/admin', '/admin/'].includes(window.location.pathname)) $wire.checkPeerStatus()"
|
||||
>
|
||||
|
||||
@can('View:SyncPage')
|
||||
@if($peerChecked)
|
||||
<span class="text-xs font-medium px-1 whitespace-nowrap"
|
||||
style="{{ $peerConnected ? 'color: #15803d' : 'color: #dc2626' }}"
|
||||
x-bind:style="{{ json_encode($peerConnected) }} ? ($store.theme === 'dark' ? 'color: #4ade80' : 'color: #15803d') : ($store.theme === 'dark' ? 'color: #f87171' : 'color: #dc2626')"
|
||||
@if($peerPath) title="{{ $peerPath }}" @endif>
|
||||
{{ $peerConnected ? __('navigation.sync.peer_connected') : __('navigation.sync.peer_disconnected') }}
|
||||
</span>
|
||||
@endif
|
||||
@endcan
|
||||
|
||||
<button
|
||||
x-data
|
||||
@@ -26,9 +40,17 @@ class="relative"
|
||||
failed: false,
|
||||
interval: null,
|
||||
|
||||
showBackupProgress: false,
|
||||
backupProgress: 0,
|
||||
backupStatusText: '',
|
||||
backupDone: false,
|
||||
backupInterval: null,
|
||||
backupUrl: null,
|
||||
|
||||
confirmSync() {
|
||||
this.open = false;
|
||||
this.showConfirm = true;
|
||||
$wire.checkPeerStatus();
|
||||
},
|
||||
|
||||
startSync() {
|
||||
@@ -60,6 +82,33 @@ class="relative"
|
||||
if (this.done) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
startBackup() {
|
||||
this.open = false;
|
||||
this.showBackupProgress = true;
|
||||
this.backupProgress = 5;
|
||||
this.backupDone = false;
|
||||
this.backupUrl = null;
|
||||
this.backupStatusText = '{{ __('navigation.backup.status_preparing') }}';
|
||||
|
||||
this.backupInterval = setInterval(() => {
|
||||
if (this.backupProgress < 80) {
|
||||
this.backupProgress += Math.random() * 8;
|
||||
if (this.backupProgress > 30) {
|
||||
this.backupStatusText = '{{ __('navigation.backup.status_compressing') }}';
|
||||
}
|
||||
}
|
||||
}, 400);
|
||||
|
||||
$wire.runBackup();
|
||||
},
|
||||
|
||||
closeBackupModal() {
|
||||
this.showBackupProgress = false;
|
||||
if (this.backupUrl) {
|
||||
window.location.href = this.backupUrl;
|
||||
}
|
||||
}
|
||||
}"
|
||||
x-on:sync-finished.window="
|
||||
@@ -73,6 +122,13 @@ class="relative"
|
||||
failed = true;
|
||||
}
|
||||
"
|
||||
x-on:backup-ready.window="
|
||||
clearInterval(backupInterval);
|
||||
backupProgress = 100;
|
||||
backupStatusText = '{{ __('navigation.backup.status_ready') }}';
|
||||
backupDone = true;
|
||||
backupUrl = $event.detail.url;
|
||||
"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
<button
|
||||
@@ -114,8 +170,7 @@ class="flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="open = false"
|
||||
wire:click="runBackup"
|
||||
@click="startBackup()"
|
||||
class="flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition w-full"
|
||||
>
|
||||
<x-heroicon-o-arrow-down-tray class="w-4 h-4 text-success-500 shrink-0" />
|
||||
@@ -222,6 +277,61 @@ class="px-4 py-2 text-sm font-medium text-white rounded-lg transition"
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-teleport="body">
|
||||
<div
|
||||
x-show="showBackupProgress"
|
||||
x-transition.opacity
|
||||
dir="rtl"
|
||||
class="fixed inset-0 z-999 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
style="display:none"
|
||||
>
|
||||
<div style="position:relative; background:white; border-radius:1rem; box-shadow:0 20px 60px rgba(0,0,0,.2); width:100%; max-width:28rem; margin:0 1rem; overflow:hidden" class="dark:bg-gray-800">
|
||||
{{-- Close button: top-left, completely independent of layout --}}
|
||||
<button
|
||||
@click="showBackupProgress = false; clearInterval(backupInterval)"
|
||||
style="position:absolute; left:12px; top:12px; z-index:10"
|
||||
class="flex items-center justify-center w-9 h-9 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:text-gray-200 dark:hover:bg-gray-700 transition"
|
||||
>
|
||||
<x-heroicon-o-x-mark class="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
<div class="px-6 pt-6 pb-4">
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-full bg-success-50 dark:bg-success-900/30">
|
||||
<template x-if="!backupDone">
|
||||
<svg class="w-5 h-5 text-success-500 animate-spin" 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 12h4z"></path>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="backupDone">
|
||||
<x-heroicon-o-check-circle class="w-5 h-5 text-success-500" />
|
||||
</template>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ __('navigation.backup.label') }}</h3>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3 mb-3 overflow-hidden">
|
||||
<div
|
||||
class="h-3 rounded-full transition-all duration-500 ease-out bg-success-500"
|
||||
:style="'width: ' + Math.min(backupProgress, 100) + '%'"
|
||||
></div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1" x-text="backupStatusText"></p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="Math.round(Math.min(backupProgress, 100)) + '%'"></p>
|
||||
</div>
|
||||
<div class="flex items-center justify-end px-6 py-4 bg-gray-50 dark:bg-gray-900/50">
|
||||
<button
|
||||
x-show="backupDone"
|
||||
@click="showBackupProgress = false; window.location.href = backupUrl"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-success-600 hover:bg-success-700 rounded-lg transition"
|
||||
>
|
||||
{{ __('navigation.backup.download_button') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
.btn-active { background: #16a34a; color: #fff; cursor: default; }
|
||||
.btn-admission{ background: #d97706; color: #fff; }
|
||||
.btn-lab { background: #6b7280; color: #fff; }
|
||||
.btn-both { background: #7c3aed; color: #fff; }
|
||||
.btn-back { background: #374151; color: #d1d5db; }
|
||||
|
||||
.toolbar-divider { flex: 1; }
|
||||
@@ -232,6 +233,10 @@
|
||||
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
||||
پرینت آزمایش
|
||||
</a>
|
||||
<a class="toolbar-btn btn-both"
|
||||
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'both']) }}">
|
||||
نسخه + آزمایش
|
||||
</a>
|
||||
|
||||
<div class="toolbar-divider"></div>
|
||||
|
||||
|
||||
@@ -268,6 +268,11 @@
|
||||
|
||||
.print-section {
|
||||
width: 210mm !important;
|
||||
height: 148mm !important;
|
||||
overflow: hidden !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.prescription-container {
|
||||
@@ -306,16 +311,16 @@
|
||||
<a class="toolbar-btn btn-print"
|
||||
href="{{ route('prescription.print', $prescription) }}">
|
||||
نسخه تنها
|
||||
</a>
|
||||
<a class="toolbar-btn btn-admission"
|
||||
href="{{ route('prescription.print.admission', $prescription) }}">
|
||||
پرینت نسخه پذیرش
|
||||
</a>
|
||||
<a class="toolbar-btn btn-lab"
|
||||
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
||||
آزمایش تنها
|
||||
</a>
|
||||
<span class="toolbar-btn btn-active">✓ نسخه + آزمایش</span>
|
||||
<a class="toolbar-btn btn-admission"
|
||||
href="{{ route('prescription.print.admission', $prescription) }}">
|
||||
پرینت نسخه پذیرش
|
||||
</a>
|
||||
@elseif ($type === 'prescription')
|
||||
<span class="toolbar-btn btn-active">✓ پرینت نسخه</span>
|
||||
<a class="toolbar-btn btn-admission"
|
||||
|
||||
Reference in New Issue
Block a user