matab-panel/app/Providers/Filament/AdminPanelProvider.php

351 lines
17 KiB
PHP

<?php
namespace App\Providers\Filament;
use App\Filament\Pages\Auth\Login;
use App\Http\Middleware\SetLocale;
use BezhanSalleh\FilamentShield\FilamentShieldPlugin;
use Filament\Actions\Action;
use Filament\Http\Middleware\Authenticate;
use Filament\View\PanelsRenderHook;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Navigation\NavigationGroup;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Support\Enums\Width;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider
{
public function boot(): void
{
\Filament\Support\Facades\FilamentAsset::register([
\Filament\Support\Assets\Js::make('audio-speed', asset('js/audio-speed.js')),
]);
}
public function panel(Panel $panel): Panel
{
return $panel
->default()
->id('admin')
->path('admin')
->login(Login::class)
->colors([
'primary' => Color::Blue,
'orange' => Color::Orange,
])
->brandLogo(function () {
$key = app()->getLocale() === 'fa' ? 'listlogo_per.png' : 'listlogo_eng.png';
$attachment = \Illuminate\Support\Facades\Cache::remember(
'brand_logo_' . $key,
now()->addHours(24),
fn () => \App\Models\OsurgInitial::where('init_parameter', $key)->value('attachment')
);
return $attachment ? asset('storage/' . $attachment) : null;
})
->brandLogoHeight('1.75rem')
->brandName('')
->favicon(asset('favicon.svg'))
->font('Vazirmatn', url: \Illuminate\Support\Facades\Vite::asset('resources/css/vazirmatn.css'), provider: \Filament\FontProviders\LocalFontProvider::class)
->renderHook(
PanelsRenderHook::BODY_START,
fn (): string => <<<'HTML'
<script>
(function () {
if (localStorage.getItem('isOpen') === null) {
localStorage.setItem('isOpen', 'false');
}
if (localStorage.getItem('isOpenDesktop') === null) {
localStorage.setItem('isOpenDesktop', 'false');
}
})();
</script>
HTML,
)
->renderHook(
PanelsRenderHook::TOPBAR_LOGO_AFTER,
fn (): string => view('components.persian-datetime')->render(),
)
->renderHook(
PanelsRenderHook::GLOBAL_SEARCH_BEFORE,
fn (): string => \Blade::render('@livewire(\'settings-dropdown\')'),
)
->renderHook(
PanelsRenderHook::BODY_END,
fn (): string => <<<'HTML'
<script>
(function () {
var speeds = [0.5, 1, 1.5, 2];
document.addEventListener('click', function (e) {
var btn = e.target.closest('.filepond--media-player-button-playbackrate');
if (!btn) return;
var item = btn.closest('.filepond--item, .filepond--root');
if (!item) return;
var media = item.querySelector('audio, video');
if (!media) return;
var current = media.playbackRate || 1;
var idx = speeds.indexOf(current);
var next = speeds[(idx + 1) % speeds.length];
media.playbackRate = next;
btn.textContent = next + 'x';
});
})();
</script>
HTML,
)
->renderHook(
PanelsRenderHook::BODY_END,
fn (): string => <<<'HTML'
<div id="fi-lightbox">
<div id="fi-lb-wrap">
<img id="fi-lb-img" src="" alt="">
<button id="fi-lb-close" title="بستن"></button>
<button id="fi-lb-prev" title="قبلی">&#8249;</button>
<button id="fi-lb-next" title="بعدی">&#8250;</button>
<div id="fi-lb-counter">1 / 1</div>
<div id="fi-lb-hint">اسکرول: زوم &nbsp;|&nbsp; دابل‌کلیک: ریست</div>
</div>
</div>
<script>
(function () {
'use strict';
var lb = document.getElementById('fi-lightbox');
var lbWrap = document.getElementById('fi-lb-wrap');
var lbImg = document.getElementById('fi-lb-img');
var lbCnt = document.getElementById('fi-lb-counter');
var lbPrev = document.getElementById('fi-lb-prev');
var lbNext = document.getElementById('fi-lb-next');
var lbClose = document.getElementById('fi-lb-close');
if (!lb) return;
var images = [], currentIdx = 0, zoom = 1;
var isPanning = false, panStartX = 0, panStartY = 0, panX = 0, panY = 0;
function applyTransform() {
lbImg.style.transform = 'scale(' + zoom + ') translate(' + panX + 'px, ' + panY + 'px)';
lbImg.classList.toggle('lb-zoomed', zoom > 1);
}
function showLightbox(imgs, idx) {
images = imgs;
currentIdx = idx;
zoom = 1; panX = 0; panY = 0;
applyTransform();
updateCounter();
loadImage();
lb.classList.add('lb-open');
document.addEventListener('keydown', onKey);
}
function hideLightbox() {
lb.classList.remove('lb-open');
document.removeEventListener('keydown', onKey);
lbImg.src = '';
}
function navigate(dir) {
if (images.length < 2) return;
currentIdx = (currentIdx + dir + images.length) % images.length;
zoom = 1; panX = 0; panY = 0;
applyTransform();
loadImage();
updateCounter();
}
function loadImage() {
lbImg.style.opacity = '0';
var src = images[currentIdx];
var tmp = new Image();
tmp.onload = function () { lbImg.src = src; lbImg.style.opacity = '1'; };
tmp.onerror = function () { lbImg.src = src; lbImg.style.opacity = '1'; };
tmp.src = src;
}
function updateCounter() {
lbCnt.textContent = (currentIdx + 1) + ' / ' + images.length;
var show = images.length > 1;
lbPrev.style.visibility = show ? '' : 'hidden';
lbNext.style.visibility = show ? '' : 'hidden';
}
function onKey(e) {
if (e.key === 'Escape') hideLightbox();
else if (e.key === 'ArrowLeft') navigate(-1);
else if (e.key === 'ArrowRight') navigate(1);
else if (e.key === '+' || e.key === '=') { zoom = Math.min(8, zoom + 0.2); applyTransform(); }
else if (e.key === '-') { zoom = Math.max(0.3, zoom - 0.2); if (zoom <= 1) { panX = 0; panY = 0; } applyTransform(); }
else if (e.key === '0') { zoom = 1; panX = 0; panY = 0; applyTransform(); }
}
lbWrap.addEventListener('wheel', function (e) {
e.preventDefault();
zoom = Math.max(0.3, Math.min(8, zoom + (e.deltaY < 0 ? 0.1 : -0.1)));
if (zoom <= 1) { panX = 0; panY = 0; }
applyTransform();
}, { passive: false });
lbImg.addEventListener('dblclick', function () {
zoom = 1; panX = 0; panY = 0; applyTransform();
});
lbImg.addEventListener('mousedown', function (e) {
if (zoom <= 1) return;
isPanning = true;
panStartX = e.clientX - panX * zoom;
panStartY = e.clientY - panY * zoom;
lbImg.classList.add('lb-dragging');
e.preventDefault();
});
document.addEventListener('mousemove', function (e) {
if (!isPanning) return;
panX = (e.clientX - panStartX) / zoom;
panY = (e.clientY - panStartY) / zoom;
lbImg.style.transform = 'scale(' + zoom + ') translate(' + panX + 'px, ' + panY + 'px)';
});
document.addEventListener('mouseup', function () {
if (isPanning) { isPanning = false; lbImg.classList.remove('lb-dragging'); }
});
lbWrap.addEventListener('click', function (e) { if (e.target === lbWrap) hideLightbox(); });
lbClose.addEventListener('click', hideLightbox);
lbPrev.addEventListener('click', function () { navigate(-1); });
lbNext.addEventListener('click', function () { navigate(1); });
document.addEventListener('click', function (e) {
var thumb = e.target.closest('[data-lb-src]');
if (!thumb) return;
e.preventDefault();
var group = thumb.getAttribute('data-lb-group');
var all = group
? Array.from(document.querySelectorAll('[data-lb-group="' + group + '"]'))
: [thumb];
var urls = all.map(function (el) { return el.getAttribute('data-lb-src'); });
var idx = all.indexOf(thumb);
showLightbox(urls, Math.max(0, idx));
});
document.addEventListener('click', function (e) {
var preview = e.target.closest('.filepond--image-preview-wrapper, .filepond--image-preview');
if (!preview) return;
var item = preview.closest('.filepond--item');
var root = item && item.closest('.filepond--root');
if (!item || !root) return;
e.preventDefault();
e.stopPropagation();
var allItems = Array.from(root.querySelectorAll('.filepond--item'));
var clickedIdx = allItems.indexOf(item);
var pond = null;
var alpineEl = root.closest('[x-data]');
if (alpineEl && window.Alpine) {
try { var ad = Alpine.$data(alpineEl); if (ad && ad.pond) pond = ad.pond; } catch (e1) {}
}
if (!pond) {
var inp = root.querySelector('input[type="file"], input.filepond');
if (inp && inp._filepond) pond = inp._filepond;
}
if (pond) {
try {
var pondFiles = pond.getFiles();
var urls1 = [];
pondFiles.forEach(function (f) {
var sid = f.serverId;
if (!sid) return;
if (typeof sid !== 'string') { try { sid = JSON.parse(sid); } catch(pe) {} }
if (typeof sid === 'string') {
urls1.push(sid.startsWith('http') || sid.startsWith('/') ? sid : '/storage/' + sid);
}
});
if (urls1.length) { showLightbox(urls1, Math.min(clickedIdx, urls1.length - 1)); return; }
} catch (e2) {}
}
var wireInput = root.querySelector('input[wire\\:model], input[wire\\:model\\.live], input[wire\\:model\\.blur]');
var wireModel = wireInput && (wireInput.getAttribute('wire:model') || wireInput.getAttribute('wire:model.live') || wireInput.getAttribute('wire:model.blur'));
var wireEl = root.closest('[wire\\:id]');
var wireId = wireEl && wireEl.getAttribute('wire:id');
if (wireModel && wireId && window.Livewire) {
try {
var comp = window.Livewire.find(wireId);
var files = comp.$wire.get(wireModel);
if (files) {
var arr = Array.isArray(files) ? files : Object.values(files);
if (arr.length) {
showLightbox(arr.map(function (f) { return '/storage/' + f; }), Math.min(clickedIdx, arr.length - 1));
return;
}
}
} catch (e3) {}
}
var imgs = [], fbIdx = 0;
allItems.forEach(function (it) {
var canvas = it.querySelector('canvas');
if (canvas) {
try {
var src = canvas.toDataURL('image/jpeg', 0.95);
if (it === item) fbIdx = imgs.length;
imgs.push(src);
} catch (ex) {}
}
});
if (imgs.length) showLightbox(imgs, fbIdx);
}, true);
})();
</script>
HTML,
)
->userMenuItems([
Action::make('switch_language')
->label(fn () => app()->getLocale() === 'fa' ? 'English' : 'فارسی')
->icon('heroicon-o-language')
->url(fn () => route('locale.switch', app()->getLocale() === 'fa' ? 'en' : 'fa')),
])
->navigationGroups([
NavigationGroup::make('بیماران و ویزیت'),
NavigationGroup::make('بیماری‌ها و روش‌های درمان'),
NavigationGroup::make('اطلاعات کلینیک'),
NavigationGroup::make('تنظیمات'),
])
->sidebarFullyCollapsibleOnDesktop()
->maxContentWidth(Width::Full)
->viteTheme('resources/css/filament/admin/theme.css')
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
->plugins([
FilamentShieldPlugin::make(),
])
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
->pages([])
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
->widgets([])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
SetLocale::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}