feat: patient view, media player, and rich editor enhancements
This commit is contained in:
parent
95592aa510
commit
063b20d26f
12
.env.example
12
.env.example
|
|
@ -1,7 +1,7 @@
|
||||||
APP_NAME=Laravel
|
APP_NAME=Laravel
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=base64:Cwol1ZZBqRAY2Em+k6lDh8PvD2RkmfSCSR5c3t+ypjg=
|
APP_KEY=base64:Cwol1ZZBqRAY2Em+k6lDh8PvD2RkmfSCSR5c3t+ypjg=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=false
|
||||||
|
|
||||||
APP_LOCALE=fa
|
APP_LOCALE=fa
|
||||||
APP_FALLBACK_LOCALE=en
|
APP_FALLBACK_LOCALE=en
|
||||||
|
|
@ -10,14 +10,14 @@ APP_FAKER_LOCALE=en_US
|
||||||
APP_MAINTENANCE_DRIVER=file
|
APP_MAINTENANCE_DRIVER=file
|
||||||
# APP_MAINTENANCE_STORE=database
|
# APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
# PHP_CLI_SERVER_WORKERS=4
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
BCRYPT_ROUNDS=12
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
LOG_STACK=single
|
LOG_STACK=single
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=warning
|
||||||
|
|
||||||
DB_CONNECTION=sqlite
|
DB_CONNECTION=sqlite
|
||||||
# DB_HOST=127.0.0.1
|
# DB_HOST=127.0.0.1
|
||||||
|
|
@ -26,7 +26,7 @@ DB_CONNECTION=sqlite
|
||||||
# DB_USERNAME=root
|
# DB_USERNAME=root
|
||||||
# DB_PASSWORD=
|
# DB_PASSWORD=
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=file
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=false
|
SESSION_ENCRYPT=false
|
||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
|
|
@ -36,7 +36,7 @@ BROADCAST_CONNECTION=log
|
||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
QUEUE_CONNECTION=database
|
QUEUE_CONNECTION=database
|
||||||
|
|
||||||
CACHE_STORE=database
|
CACHE_STORE=file
|
||||||
# CACHE_PREFIX=
|
# CACHE_PREFIX=
|
||||||
|
|
||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
@ -63,7 +63,7 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|
||||||
APP_URL=http://localhost:8000
|
# APP_URL auto-detected from request — see AppServiceProvider
|
||||||
|
|
||||||
SYNC_PEER_IP=192.168.1.8
|
SYNC_PEER_IP=192.168.1.8
|
||||||
SYNC_PEER_PORT=8000
|
SYNC_PEER_PORT=8000
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Infolists\Components;
|
||||||
|
|
||||||
|
use Filament\Infolists\Components\Entry;
|
||||||
|
use Filament\Support\Components\Contracts\HasEmbeddedView;
|
||||||
|
|
||||||
|
class ExpandableHtmlEntry extends Entry implements HasEmbeddedView
|
||||||
|
{
|
||||||
|
protected int $limit = 200;
|
||||||
|
|
||||||
|
public function limit(int $limit): static
|
||||||
|
{
|
||||||
|
$this->limit = $limit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toEmbeddedHtml(): string
|
||||||
|
{
|
||||||
|
$state = $this->getState() ?? '';
|
||||||
|
$plain = strip_tags($state);
|
||||||
|
|
||||||
|
if (! $plain) {
|
||||||
|
return $this->wrapEmbeddedHtml('<span class="text-sm text-gray-400">-</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mb_strlen($plain) <= $this->limit) {
|
||||||
|
return $this->wrapEmbeddedHtml('<div class="prose max-w-none">' . $state . '</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = view('livewire.expandable-text-embed', [
|
||||||
|
'html' => $state,
|
||||||
|
'key' => 'et-' . substr(md5($state), 0, 8),
|
||||||
|
])->render();
|
||||||
|
|
||||||
|
return $this->wrapEmbeddedHtml($html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Infolists\Components;
|
||||||
|
|
||||||
|
use Filament\Infolists\Components\Entry;
|
||||||
|
use Filament\Support\Components\Contracts\HasEmbeddedView;
|
||||||
|
|
||||||
|
class MediaFilesEntry extends Entry implements HasEmbeddedView
|
||||||
|
{
|
||||||
|
public function toEmbeddedHtml(): string
|
||||||
|
{
|
||||||
|
$record = $this->getRecord();
|
||||||
|
$field = $this->getName();
|
||||||
|
|
||||||
|
if (! $record) {
|
||||||
|
return $this->wrapEmbeddedHtml('<span class="text-sm text-gray-400">-</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $record->{$field} ?? [];
|
||||||
|
$files = is_array($files) ? array_filter($files) : [];
|
||||||
|
|
||||||
|
if (empty($files)) {
|
||||||
|
return $this->wrapEmbeddedHtml('<span class="text-sm text-gray-400">-</span>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = view('filament.infolists.media-player-embed', [
|
||||||
|
'patientId' => $record->id,
|
||||||
|
'field' => $field,
|
||||||
|
])->render();
|
||||||
|
|
||||||
|
return $this->wrapEmbeddedHtml($html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Infolists\Components;
|
||||||
|
|
||||||
|
use Filament\Infolists\Components\Entry;
|
||||||
|
use Filament\Support\Components\Contracts\HasEmbeddedView;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class PhotoGalleryEntry extends Entry implements HasEmbeddedView
|
||||||
|
{
|
||||||
|
public function toEmbeddedHtml(): string
|
||||||
|
{
|
||||||
|
$images = $this->getState() ?? [];
|
||||||
|
$images = is_array($images) ? array_values(array_filter($images)) : [];
|
||||||
|
$urls = array_values(array_map(
|
||||||
|
fn ($path) => Storage::disk('public')->url($path),
|
||||||
|
$images
|
||||||
|
));
|
||||||
|
|
||||||
|
if (empty($urls)) {
|
||||||
|
$html = '<span class="text-sm text-gray-400">-</span>';
|
||||||
|
|
||||||
|
return $this->wrapEmbeddedHtml($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = $this->getRecord();
|
||||||
|
$caption = match ($this->getName()) {
|
||||||
|
'photos_before' => $record?->surgery_before,
|
||||||
|
'photos_after' => $record?->sergery_after,
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
$section = match ($this->getName()) {
|
||||||
|
'photos_before' => 'before',
|
||||||
|
'photos_after' => 'after',
|
||||||
|
default => '',
|
||||||
|
};
|
||||||
|
$captionAttr = $caption ? ' data-lb-caption="' . htmlspecialchars($caption, ENT_QUOTES, 'UTF-8') . '"' : '';
|
||||||
|
$sectionAttr = $section ? ' data-lb-section="' . $section . '"' : '';
|
||||||
|
|
||||||
|
$group = 'gallery-' . md5(implode(',', $urls));
|
||||||
|
$items = '';
|
||||||
|
|
||||||
|
foreach ($urls as $idx => $url) {
|
||||||
|
$esc = e($url);
|
||||||
|
$items .= <<<HTML
|
||||||
|
<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="{$esc}"{$captionAttr}{$sectionAttr}
|
||||||
|
>
|
||||||
|
<img src="{$esc}" 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>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<div class="grid grid-cols-4 gap-2">' . $items . '</div>';
|
||||||
|
|
||||||
|
return $this->wrapEmbeddedHtml($html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,18 +20,26 @@
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Filament\Forms\Components\CheckboxList;
|
use Filament\Forms\Components\CheckboxList;
|
||||||
use Filament\Forms\Components\DatePicker;
|
use Filament\Forms\Components\DatePicker;
|
||||||
use Filament\Forms\Components\DateTimePicker;
|
use Filament\Forms\Components\DateTimePicker;
|
||||||
use Filament\Forms\Components\FileUpload;
|
use Filament\Forms\Components\FileUpload;
|
||||||
use Filament\Forms\Components\Radio;
|
use Filament\Forms\Components\Radio;
|
||||||
|
use App\Filament\RichEditor\HighlightColorPlugin;
|
||||||
use Filament\Forms\Components\RichEditor;
|
use Filament\Forms\Components\RichEditor;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Components\Textarea;
|
use Filament\Forms\Components\Textarea;
|
||||||
use Filament\Forms\Components\TimePicker;
|
use Filament\Forms\Components\TimePicker;
|
||||||
|
use Filament\Forms\Components\Placeholder;
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Forms\Get;
|
||||||
|
use App\Filament\Infolists\Components\ExpandableHtmlEntry;
|
||||||
|
use App\Filament\Infolists\Components\MediaFilesEntry;
|
||||||
|
use App\Filament\Infolists\Components\PhotoGalleryEntry;
|
||||||
|
use Filament\Infolists\Components\TextEntry;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
use Filament\Schemas\Components\Grid;
|
use Filament\Schemas\Components\Grid;
|
||||||
use Filament\Schemas\Components\Section;
|
use Filament\Schemas\Components\Section;
|
||||||
|
|
@ -54,9 +62,63 @@ class PatientResource extends Resource
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'first_name';
|
protected static ?string $recordTitleAttribute = 'first_name';
|
||||||
|
|
||||||
|
public static function previewHighlightedHtml(string $state, int $limit = 30): string
|
||||||
|
{
|
||||||
|
$plain = strip_tags($state);
|
||||||
|
if (! $plain) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$suffix = '<span class="text-primary-600 text-xs font-semibold cursor-pointer">... بیشتر</span>';
|
||||||
|
|
||||||
|
$preview = preg_replace('/<(?!\/?span[^>]*data-hbg|\/?mark\b)[^>]+>/i', '', $state);
|
||||||
|
$preview = trim(preg_replace('/\s+/', ' ', $preview));
|
||||||
|
|
||||||
|
if (mb_strlen($plain) <= $limit) {
|
||||||
|
return $preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
$count = 0;
|
||||||
|
$inTag = false;
|
||||||
|
$tagBuf = '';
|
||||||
|
$openSpans = 0;
|
||||||
|
|
||||||
|
foreach (mb_str_split($preview) as $ch) {
|
||||||
|
if ($count >= $limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($ch === '<') {
|
||||||
|
$inTag = true;
|
||||||
|
$tagBuf = '<';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($inTag) {
|
||||||
|
$tagBuf .= $ch;
|
||||||
|
if ($ch === '>') {
|
||||||
|
$inTag = false;
|
||||||
|
$result .= $tagBuf;
|
||||||
|
if (str_contains($tagBuf, '</')) {
|
||||||
|
$openSpans = max(0, $openSpans - 1);
|
||||||
|
} else {
|
||||||
|
$openSpans++;
|
||||||
|
}
|
||||||
|
$tagBuf = '';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result .= $ch;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result .= str_repeat('</span>', $openSpans);
|
||||||
|
|
||||||
|
return $result . ' ' . $suffix;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getGloballySearchableAttributes(): array
|
public static function getGloballySearchableAttributes(): array
|
||||||
{
|
{
|
||||||
return ['first_name', 'last_name', 'hand_phone'];
|
return ['first_name', 'last_name', 'hand_phone', 'icno'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
|
public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
|
||||||
|
|
@ -287,9 +349,10 @@ public static function form(Schema $schema): Schema
|
||||||
RichEditor::make('surgery_before')
|
RichEditor::make('surgery_before')
|
||||||
->label(__('patients.fields.surgery_before'))
|
->label(__('patients.fields.surgery_before'))
|
||||||
->extraInputAttributes(['style' => 'min-height: 150px'])
|
->extraInputAttributes(['style' => 'min-height: 150px'])
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
->toolbarButtons([
|
->toolbarButtons([
|
||||||
['bold', 'italic', 'underline', 'strike', 'link'],
|
['bold', 'italic', 'underline', 'strike', 'link'],
|
||||||
['highlight'],
|
['highlightYellow', 'highlightRed'],
|
||||||
['h2', 'h3', 'alignStart', 'alignCenter', 'alignEnd'],
|
['h2', 'h3', 'alignStart', 'alignCenter', 'alignEnd'],
|
||||||
['blockquote', 'bulletList', 'orderedList'],
|
['blockquote', 'bulletList', 'orderedList'],
|
||||||
['undo', 'redo'],
|
['undo', 'redo'],
|
||||||
|
|
@ -298,9 +361,10 @@ public static function form(Schema $schema): Schema
|
||||||
RichEditor::make('sergery_after')
|
RichEditor::make('sergery_after')
|
||||||
->label(__('patients.fields.sergery_after'))
|
->label(__('patients.fields.sergery_after'))
|
||||||
->extraInputAttributes(['style' => 'min-height: 150px'])
|
->extraInputAttributes(['style' => 'min-height: 150px'])
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
->toolbarButtons([
|
->toolbarButtons([
|
||||||
['bold', 'italic', 'underline', 'strike', 'link'],
|
['bold', 'italic', 'underline', 'strike', 'link'],
|
||||||
['highlight'],
|
['highlightYellow', 'highlightRed'],
|
||||||
['h2', 'h3', 'alignStart', 'alignCenter', 'alignEnd'],
|
['h2', 'h3', 'alignStart', 'alignCenter', 'alignEnd'],
|
||||||
['blockquote', 'bulletList', 'orderedList'],
|
['blockquote', 'bulletList', 'orderedList'],
|
||||||
['undo', 'redo'],
|
['undo', 'redo'],
|
||||||
|
|
@ -316,7 +380,10 @@ public static function form(Schema $schema): Schema
|
||||||
->multiple()
|
->multiple()
|
||||||
->image()
|
->image()
|
||||||
->panelLayout('grid')
|
->panelLayout('grid')
|
||||||
->extraAttributes(['class' => 'fi-photo-gallery'])
|
->extraAttributes(fn (string $operation) => $operation === 'view'
|
||||||
|
? ['class' => 'fi-photo-gallery fi-photo-gallery-view', 'data-hide-dropzone' => 'true']
|
||||||
|
: ['class' => 'fi-photo-gallery']
|
||||||
|
)
|
||||||
->disk('public')
|
->disk('public')
|
||||||
->directory(function ($record) {
|
->directory(function ($record) {
|
||||||
$year = \Morilog\Jalali\Jalalian::now()->getYear();
|
$year = \Morilog\Jalali\Jalalian::now()->getYear();
|
||||||
|
|
@ -325,6 +392,8 @@ public static function form(Schema $schema): Schema
|
||||||
})
|
})
|
||||||
->downloadable()
|
->downloadable()
|
||||||
->openable()
|
->openable()
|
||||||
|
->disabled(fn (string $operation) => $operation === 'view')
|
||||||
|
->deletable(fn (string $operation) => $operation !== 'view')
|
||||||
->fetchFileInformation(false),
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
FileUpload::make('photos_after')
|
FileUpload::make('photos_after')
|
||||||
|
|
@ -332,7 +401,10 @@ public static function form(Schema $schema): Schema
|
||||||
->multiple()
|
->multiple()
|
||||||
->image()
|
->image()
|
||||||
->panelLayout('grid')
|
->panelLayout('grid')
|
||||||
->extraAttributes(['class' => 'fi-photo-gallery'])
|
->extraAttributes(fn (string $operation) => $operation === 'view'
|
||||||
|
? ['class' => 'fi-photo-gallery fi-photo-gallery-view', 'data-hide-dropzone' => 'true']
|
||||||
|
: ['class' => 'fi-photo-gallery']
|
||||||
|
)
|
||||||
->disk('public')
|
->disk('public')
|
||||||
->directory(function ($record) {
|
->directory(function ($record) {
|
||||||
$year = \Morilog\Jalali\Jalalian::now()->getYear();
|
$year = \Morilog\Jalali\Jalalian::now()->getYear();
|
||||||
|
|
@ -341,6 +413,8 @@ public static function form(Schema $schema): Schema
|
||||||
})
|
})
|
||||||
->downloadable()
|
->downloadable()
|
||||||
->openable()
|
->openable()
|
||||||
|
->disabled(fn (string $operation) => $operation === 'view')
|
||||||
|
->deletable(fn (string $operation) => $operation !== 'view')
|
||||||
->fetchFileInformation(false),
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
FileUpload::make('videos')
|
FileUpload::make('videos')
|
||||||
|
|
@ -363,8 +437,14 @@ public static function form(Schema $schema): Schema
|
||||||
])
|
])
|
||||||
->maxSize(102400)
|
->maxSize(102400)
|
||||||
->downloadable()
|
->downloadable()
|
||||||
|
->openable()
|
||||||
->panelLayout('grid')
|
->panelLayout('grid')
|
||||||
->extraAttributes(['class' => 'fi-video-gallery'])
|
->extraAttributes(fn (string $operation) => $operation === 'view'
|
||||||
|
? ['class' => 'fi-video-gallery', 'data-hide-dropzone' => 'true']
|
||||||
|
: ['class' => 'fi-video-gallery']
|
||||||
|
)
|
||||||
|
->disabled(fn (string $operation) => $operation === 'view')
|
||||||
|
->deletable(fn (string $operation) => $operation !== 'view')
|
||||||
->fetchFileInformation(false),
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
FileUpload::make('audio_files')
|
FileUpload::make('audio_files')
|
||||||
|
|
@ -384,6 +464,13 @@ public static function form(Schema $schema): Schema
|
||||||
'audio/aiff', 'audio/x-aiff',
|
'audio/aiff', 'audio/x-aiff',
|
||||||
])
|
])
|
||||||
->downloadable()
|
->downloadable()
|
||||||
|
->openable()
|
||||||
|
->extraAttributes(fn (string $operation) => $operation === 'view'
|
||||||
|
? ['data-hide-dropzone' => 'true']
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
->disabled(fn (string $operation) => $operation === 'view')
|
||||||
|
->deletable(fn (string $operation) => $operation !== 'view')
|
||||||
->fetchFileInformation(false),
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
])
|
])
|
||||||
|
|
@ -421,6 +508,186 @@ public static function form(Schema $schema): Schema
|
||||||
->color('gray')
|
->color('gray')
|
||||||
->visible($operation === 'edit'),
|
->visible($operation === 'edit'),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
->columnSpanFull(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function infolist(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return $schema
|
||||||
|
->components([
|
||||||
|
Grid::make()
|
||||||
|
->schema([
|
||||||
|
Section::make(__('patients.sections.patient_info'))
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('first_name')
|
||||||
|
->label(__('patients.fields.first_name')),
|
||||||
|
TextEntry::make('last_name')
|
||||||
|
->label(__('patients.fields.last_name')),
|
||||||
|
TextEntry::make('father_name')
|
||||||
|
->label(__('patients.fields.father_name'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('icno')
|
||||||
|
->label(__('patients.fields.icno'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('hand_phone')
|
||||||
|
->label(__('patients.fields.hand_phone'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('age')
|
||||||
|
->label(__('patients.fields.age'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('birth_date')
|
||||||
|
->label(__('patients.fields.birth_date'))
|
||||||
|
->placeholder('-')
|
||||||
|
->formatStateUsing(fn ($state) => $state
|
||||||
|
? Jalalian::fromCarbon(\Carbon\Carbon::parse($state))->format('Y/m/d')
|
||||||
|
: '-'
|
||||||
|
),
|
||||||
|
TextEntry::make('gender')
|
||||||
|
->label(__('patients.fields.gender'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('marital_status')
|
||||||
|
->label(__('patients.fields.marital_status'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('insurance_no')
|
||||||
|
->label(__('patients.fields.insurance_no'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('insurance')
|
||||||
|
->label(__('patients.fields.insurance'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('education')
|
||||||
|
->label(__('patients.fields.education'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('job')
|
||||||
|
->label(__('patients.fields.job'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('home_phone')
|
||||||
|
->label(__('patients.fields.home_phone'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('work_phone')
|
||||||
|
->label(__('patients.fields.work_phone'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('other_phone')
|
||||||
|
->label(__('patients.fields.other_phone'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('home_address')
|
||||||
|
->label(__('patients.fields.home_address'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('work_address')
|
||||||
|
->label(__('patients.fields.work_address'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('refered_by')
|
||||||
|
->label(__('patients.fields.refered_by'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('doc_id')
|
||||||
|
->label(__('patients.fields.doc_id'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('referal_reason')
|
||||||
|
->label(__('patients.fields.referal_reason'))
|
||||||
|
->columnSpan(2)
|
||||||
|
->placeholder('-'),
|
||||||
|
])
|
||||||
|
->columns(4)
|
||||||
|
->columnSpanFull(),
|
||||||
|
|
||||||
|
Section::make(__('patients.sections.illness'))
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('current_illness_1')
|
||||||
|
->label(__('patients.fields.current_illness_1'))
|
||||||
|
->listWithLineBreaks()
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('current_illness_2')
|
||||||
|
->label(__('patients.fields.current_illness_2'))
|
||||||
|
->listWithLineBreaks()
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('blood_sugar')
|
||||||
|
->label(__('patients.fields.blood_sugar'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('blood_pressure')
|
||||||
|
->label(__('patients.fields.blood_pressure'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('is_undercare')
|
||||||
|
->label(__('patients.fields.is_undercare'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('undercare_reason')
|
||||||
|
->label(__('patients.fields.undercare_reason'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('is_usingdrug')
|
||||||
|
->label(__('patients.fields.is_usingdrug'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('underdrug_reason')
|
||||||
|
->label(__('patients.fields.underdrug_reason'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('has_alergyto')
|
||||||
|
->label(__('patients.fields.has_alergyto'))
|
||||||
|
->listWithLineBreaks()
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('alergy_reason')
|
||||||
|
->label(__('patients.fields.alergy_reason'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('description')
|
||||||
|
->label(__('patients.fields.description'))
|
||||||
|
->columnSpanFull()
|
||||||
|
->placeholder('-'),
|
||||||
|
])
|
||||||
|
->columns(2)
|
||||||
|
->columnSpanFull(),
|
||||||
|
|
||||||
|
Section::make(__('patients.sections.surgery_notes'))
|
||||||
|
->schema([
|
||||||
|
ExpandableHtmlEntry::make('surgery_before')
|
||||||
|
->label(__('patients.fields.surgery_before')),
|
||||||
|
ExpandableHtmlEntry::make('sergery_after')
|
||||||
|
->label(__('patients.fields.sergery_after')),
|
||||||
|
])
|
||||||
|
->columns(2)
|
||||||
|
->columnSpanFull(),
|
||||||
|
|
||||||
|
Section::make(__('patients.sections.media'))
|
||||||
|
->schema([
|
||||||
|
PhotoGalleryEntry::make('photos_before')
|
||||||
|
->label(__('patients.fields.photos_before')),
|
||||||
|
PhotoGalleryEntry::make('photos_after')
|
||||||
|
->label(__('patients.fields.photos_after')),
|
||||||
|
MediaFilesEntry::make('videos')
|
||||||
|
->label(__('patients.fields.videos')),
|
||||||
|
MediaFilesEntry::make('audio_files')
|
||||||
|
->label(__('patients.fields.audio_files')),
|
||||||
|
])
|
||||||
|
->columns(2)
|
||||||
|
->columnSpanFull(),
|
||||||
|
|
||||||
|
Section::make(__('patients.sections.record_info'))
|
||||||
|
->schema([
|
||||||
|
TextEntry::make('creator.name')
|
||||||
|
->label(__('patients.fields.created_by'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('updator.name')
|
||||||
|
->label(__('patients.fields.updated_by'))
|
||||||
|
->placeholder('-'),
|
||||||
|
TextEntry::make('surgeryAppointment.surgery_date')
|
||||||
|
->label(__('patients.fields.surgery_appointment'))
|
||||||
|
->placeholder('-')
|
||||||
|
->formatStateUsing(function ($state) {
|
||||||
|
if (! $state) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Jalalian::fromCarbon(\Carbon\Carbon::parse($state))->format('Y/m/d - H:i');
|
||||||
|
} catch (\Exception) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TextEntry::make('updated_at')
|
||||||
|
->label(__('patients.fields.updated_at'))
|
||||||
|
->placeholder('-'),
|
||||||
|
])
|
||||||
|
->columns(4)
|
||||||
|
->columnSpanFull(),
|
||||||
])
|
])
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]);
|
]);
|
||||||
|
|
@ -442,13 +709,19 @@ public static function table(Table $table): Table
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable(),
|
->sortable(),
|
||||||
|
|
||||||
|
TextColumn::make('icno')
|
||||||
|
->label(__('patients.fields.icno'))
|
||||||
|
->searchable()
|
||||||
|
->placeholder('-'),
|
||||||
TextColumn::make('hand_phone')
|
TextColumn::make('hand_phone')
|
||||||
->label(__('patients.fields.hand_phone'))
|
->label(__('patients.fields.hand_phone'))
|
||||||
|
->searchable()
|
||||||
->placeholder('-'),
|
->placeholder('-'),
|
||||||
|
|
||||||
TextColumn::make('home_phone')
|
TextColumn::make('home_phone')
|
||||||
->label(__('patients.fields.home_phone'))
|
->label(__('patients.fields.home_phone'))
|
||||||
->placeholder('-'),
|
->placeholder('-')
|
||||||
|
->hidden(),
|
||||||
|
|
||||||
TextColumn::make('creator.name')
|
TextColumn::make('creator.name')
|
||||||
->label(__('patients.fields.created_by'))
|
->label(__('patients.fields.created_by'))
|
||||||
|
|
@ -475,17 +748,7 @@ public static function table(Table $table): Table
|
||||||
? ['class' => 'pointer-events-none']
|
? ['class' => 'pointer-events-none']
|
||||||
: []
|
: []
|
||||||
)
|
)
|
||||||
->formatStateUsing(function ($state) {
|
->formatStateUsing(fn ($state) => static::previewHighlightedHtml($state ?? ''))
|
||||||
$plain = strip_tags($state ?? '');
|
|
||||||
if (! $plain) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
if (mb_strlen($plain) > 30) {
|
|
||||||
return $state
|
|
||||||
. ' <span class="text-primary-600 text-xs font-semibold cursor-pointer">... بیشتر</span>';
|
|
||||||
}
|
|
||||||
return $state;
|
|
||||||
})
|
|
||||||
->html()
|
->html()
|
||||||
->action(
|
->action(
|
||||||
Action::make('viewSurgeryBefore')
|
Action::make('viewSurgeryBefore')
|
||||||
|
|
@ -510,17 +773,7 @@ public static function table(Table $table): Table
|
||||||
? ['class' => 'pointer-events-none']
|
? ['class' => 'pointer-events-none']
|
||||||
: []
|
: []
|
||||||
)
|
)
|
||||||
->formatStateUsing(function ($state) {
|
->formatStateUsing(fn ($state) => static::previewHighlightedHtml($state ?? ''))
|
||||||
$plain = strip_tags($state ?? '');
|
|
||||||
if (! $plain) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
if (mb_strlen($plain) > 30) {
|
|
||||||
return $state
|
|
||||||
. ' <span class="text-primary-600 text-xs font-semibold cursor-pointer">... بیشتر</span>';
|
|
||||||
}
|
|
||||||
return $state;
|
|
||||||
})
|
|
||||||
->html()
|
->html()
|
||||||
->action(
|
->action(
|
||||||
Action::make('viewSergeryAfter')
|
Action::make('viewSergeryAfter')
|
||||||
|
|
@ -554,7 +807,7 @@ public static function table(Table $table): Table
|
||||||
->iconColor(fn ($state) => $state ? 'success' : 'gray')
|
->iconColor(fn ($state) => $state ? 'success' : 'gray')
|
||||||
->placeholder('-'),
|
->placeholder('-'),
|
||||||
])
|
])
|
||||||
->defaultSort('updated_at', 'desc')
|
->defaultSort('created_at', 'desc')
|
||||||
->recordActions([
|
->recordActions([
|
||||||
Action::make('surgeryAppointment')
|
Action::make('surgeryAppointment')
|
||||||
->label(__('patients.actions.surgery_appointment'))
|
->label(__('patients.actions.surgery_appointment'))
|
||||||
|
|
@ -849,6 +1102,13 @@ public static function table(Table $table): Table
|
||||||
|
|
||||||
\Filament\Forms\Components\RichEditor::make('content')
|
\Filament\Forms\Components\RichEditor::make('content')
|
||||||
->label(__('prescriptions.fields.content'))
|
->label(__('prescriptions.fields.content'))
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
|
->toolbarButtons([
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['highlightYellow', 'highlightRed'],
|
||||||
|
['bulletList', 'orderedList'],
|
||||||
|
['undo', 'redo'],
|
||||||
|
])
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
@ -889,6 +1149,13 @@ public static function table(Table $table): Table
|
||||||
|
|
||||||
\Filament\Forms\Components\RichEditor::make('lab_content')
|
\Filament\Forms\Components\RichEditor::make('lab_content')
|
||||||
->label(__('prescriptions.fields.lab_content'))
|
->label(__('prescriptions.fields.lab_content'))
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
|
->toolbarButtons([
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['highlightYellow', 'highlightRed'],
|
||||||
|
['bulletList', 'orderedList'],
|
||||||
|
['undo', 'redo'],
|
||||||
|
])
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
|
@ -974,14 +1241,18 @@ public static function table(Table $table): Table
|
||||||
$record->audio_files,
|
$record->audio_files,
|
||||||
]))
|
]))
|
||||||
)
|
)
|
||||||
->modalWidth(Width::ExtraLarge)
|
->modalWidth(Width::ThreeExtraLarge)
|
||||||
->fillForm(fn (Patient $record): array => [
|
->fillForm(fn (Patient $record): array => [
|
||||||
'photos_before' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->photos_before),
|
'photos_before' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->photos_before),
|
||||||
'photos_after' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->photos_after),
|
'photos_after' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->photos_after),
|
||||||
'videos' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->videos),
|
'videos' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->videos),
|
||||||
'audio_files' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->audio_files),
|
'audio_files' => \App\Filament\Resources\PatientResource\Pages\EditPatient::normalizeFileList($record->audio_files),
|
||||||
|
'surgery_before' => $record->surgery_before,
|
||||||
|
'sergery_after' => $record->sergery_after,
|
||||||
])
|
])
|
||||||
->schema([
|
->schema([
|
||||||
|
\Filament\Forms\Components\Hidden::make('surgery_before'),
|
||||||
|
\Filament\Forms\Components\Hidden::make('sergery_after'),
|
||||||
Grid::make(2)
|
Grid::make(2)
|
||||||
->schema([
|
->schema([
|
||||||
FileUpload::make('photos_before')
|
FileUpload::make('photos_before')
|
||||||
|
|
@ -998,7 +1269,12 @@ public static function table(Table $table): Table
|
||||||
->openable()
|
->openable()
|
||||||
->disabled()
|
->disabled()
|
||||||
->fetchFileInformation(false)
|
->fetchFileInformation(false)
|
||||||
->extraAttributes(['class' => 'fi-photo-gallery fi-photo-gallery-view', 'data-hide-dropzone' => 'true']),
|
->extraAttributes(fn ($get): array => [
|
||||||
|
'class' => 'fi-photo-gallery fi-photo-gallery-view',
|
||||||
|
'data-hide-dropzone' => 'true',
|
||||||
|
'data-lb-section' => 'before',
|
||||||
|
'data-lb-caption' => htmlspecialchars($get('surgery_before') ?? '', ENT_QUOTES, 'UTF-8'),
|
||||||
|
]),
|
||||||
|
|
||||||
FileUpload::make('photos_after')
|
FileUpload::make('photos_after')
|
||||||
->label(__('patients.fields.photos_after'))
|
->label(__('patients.fields.photos_after'))
|
||||||
|
|
@ -1014,7 +1290,12 @@ public static function table(Table $table): Table
|
||||||
->openable()
|
->openable()
|
||||||
->disabled()
|
->disabled()
|
||||||
->fetchFileInformation(false)
|
->fetchFileInformation(false)
|
||||||
->extraAttributes(['class' => 'fi-photo-gallery fi-photo-gallery-view', 'data-hide-dropzone' => 'true']),
|
->extraAttributes(fn ($get): array => [
|
||||||
|
'class' => 'fi-photo-gallery fi-photo-gallery-view',
|
||||||
|
'data-hide-dropzone' => 'true',
|
||||||
|
'data-lb-section' => 'after',
|
||||||
|
'data-lb-caption' => htmlspecialchars($get('sergery_after') ?? '', ENT_QUOTES, 'UTF-8'),
|
||||||
|
]),
|
||||||
|
|
||||||
FileUpload::make('videos')
|
FileUpload::make('videos')
|
||||||
->label(__('patients.fields.videos'))
|
->label(__('patients.fields.videos'))
|
||||||
|
|
@ -1037,7 +1318,8 @@ public static function table(Table $table): Table
|
||||||
->downloadable()
|
->downloadable()
|
||||||
->disabled()
|
->disabled()
|
||||||
->fetchFileInformation(false)
|
->fetchFileInformation(false)
|
||||||
->extraAttributes(['data-hide-dropzone' => 'true']),
|
->panelLayout('grid')
|
||||||
|
->extraAttributes(['class' => 'fi-video-gallery', 'data-hide-dropzone' => 'true']),
|
||||||
|
|
||||||
FileUpload::make('audio_files')
|
FileUpload::make('audio_files')
|
||||||
->label(__('patients.fields.audio_files'))
|
->label(__('patients.fields.audio_files'))
|
||||||
|
|
@ -1076,6 +1358,7 @@ public static function table(Table $table): Table
|
||||||
->modalSubmitAction(false)
|
->modalSubmitAction(false)
|
||||||
->modalCancelActionLabel('بستن'),
|
->modalCancelActionLabel('بستن'),
|
||||||
|
|
||||||
|
ViewAction::make()->iconButton()->color('gray'),
|
||||||
EditAction::make()->iconButton(),
|
EditAction::make()->iconButton(),
|
||||||
DeleteAction::make()->iconButton(),
|
DeleteAction::make()->iconButton(),
|
||||||
], position: \Filament\Tables\Enums\RecordActionsPosition::BeforeCells)
|
], position: \Filament\Tables\Enums\RecordActionsPosition::BeforeCells)
|
||||||
|
|
@ -1200,6 +1483,7 @@ public static function getPages(): array
|
||||||
return [
|
return [
|
||||||
'index' => Pages\ListPatients::route('/'),
|
'index' => Pages\ListPatients::route('/'),
|
||||||
'create' => Pages\CreatePatient::route('/create'),
|
'create' => Pages\CreatePatient::route('/create'),
|
||||||
|
'view' => Pages\ViewPatient::route('/{record}'),
|
||||||
'edit' => Pages\EditPatient::route('/{record}/edit'),
|
'edit' => Pages\EditPatient::route('/{record}/edit'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\PatientResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\PatientResource;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
use Filament\Support\Enums\Width;
|
||||||
|
|
||||||
|
class ViewPatient extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = PatientResource::class;
|
||||||
|
|
||||||
|
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Action::make('printPrescription')
|
||||||
|
->label(__('prescriptions.actions.print_prescription'))
|
||||||
|
->icon('heroicon-o-printer')
|
||||||
|
->color('info')
|
||||||
|
->visible(fn () => $this->getRecord()->prescriptions()->exists())
|
||||||
|
->url(fn () => route('prescription.print', $this->getRecord()->prescriptions()->latest()->first()))
|
||||||
|
->openUrlInNewTab(),
|
||||||
|
|
||||||
|
Action::make('printAdmission')
|
||||||
|
->label(__('prescriptions.actions.print_admission'))
|
||||||
|
->icon('heroicon-o-printer')
|
||||||
|
->color('warning')
|
||||||
|
->visible(fn () => $this->getRecord()->surgeryAppointment !== null)
|
||||||
|
->url(fn () => route('surgery-appointment.print.admission', $this->getRecord()))
|
||||||
|
->openUrlInNewTab(),
|
||||||
|
|
||||||
|
Action::make('printLab')
|
||||||
|
->label(__('prescriptions.actions.print_lab'))
|
||||||
|
->icon('heroicon-o-beaker')
|
||||||
|
->color('gray')
|
||||||
|
->url(fn () => route('patient.print.lab', $this->getRecord()))
|
||||||
|
->openUrlInNewTab(),
|
||||||
|
|
||||||
|
Action::make('edit')
|
||||||
|
->label(__('filament-actions::edit.single.label'))
|
||||||
|
->icon('heroicon-o-pencil-square')
|
||||||
|
->color('primary')
|
||||||
|
->url(fn () => PatientResource::getUrl('edit', ['record' => $this->getRecord()])),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use App\Filament\Resources\PatientResource\Pages\CreatePatient;
|
use App\Filament\Resources\PatientResource\Pages\CreatePatient;
|
||||||
use App\Filament\Resources\PatientResource\Pages\EditPatient;
|
use App\Filament\Resources\PatientResource\Pages\EditPatient;
|
||||||
|
use App\Filament\Resources\PatientResource\Pages\ViewPatient;
|
||||||
use App\Models\Doctor;
|
use App\Models\Doctor;
|
||||||
use App\Models\PaymentType;
|
use App\Models\PaymentType;
|
||||||
use App\Models\Treatment;
|
use App\Models\Treatment;
|
||||||
|
|
@ -32,7 +33,7 @@ class VisitsRelationManager extends RelationManager
|
||||||
|
|
||||||
public static function canViewForRecord(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): bool
|
public static function canViewForRecord(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): bool
|
||||||
{
|
{
|
||||||
return in_array($pageClass, [CreatePatient::class, EditPatient::class]);
|
return in_array($pageClass, [CreatePatient::class, EditPatient::class, ViewPatient::class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||||
|
|
@ -136,6 +137,18 @@ public function form(Schema $schema): Schema
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function jalaliDate(?string $date): string
|
||||||
|
{
|
||||||
|
if (! $date) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return \Morilog\Jalali\Jalalian::fromCarbon(\Carbon\Carbon::parse($date))->format('Y/m/d');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function table(Table $table): Table
|
public function table(Table $table): Table
|
||||||
{
|
{
|
||||||
return $table
|
return $table
|
||||||
|
|
@ -219,8 +232,10 @@ public function table(Table $table): Table
|
||||||
->recordActions([
|
->recordActions([
|
||||||
EditAction::make()
|
EditAction::make()
|
||||||
->slideOver()
|
->slideOver()
|
||||||
->modalWidth('4xl'),
|
->modalWidth('4xl')
|
||||||
DeleteAction::make(),
|
->modalHeading(fn () => __('filament-actions::edit.single.modal.heading', ['label' => $this->getOwnerRecord()->full_name])),
|
||||||
|
DeleteAction::make()
|
||||||
|
->modalHeading(fn () => __('filament-actions::delete.single.modal.heading', ['label' => $this->getOwnerRecord()->full_name])),
|
||||||
])
|
])
|
||||||
->toolbarActions([]);
|
->toolbarActions([]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
use Filament\Forms\Components\CheckboxList;
|
use Filament\Forms\Components\CheckboxList;
|
||||||
use Filament\Forms\Components\DatePicker;
|
use Filament\Forms\Components\DatePicker;
|
||||||
use Filament\Forms\Components\Hidden;
|
use Filament\Forms\Components\Hidden;
|
||||||
|
use App\Filament\RichEditor\HighlightColorPlugin;
|
||||||
use Filament\Forms\Components\RichEditor;
|
use Filament\Forms\Components\RichEditor;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
|
|
@ -164,6 +165,13 @@ public static function form(Schema $schema): Schema
|
||||||
|
|
||||||
RichEditor::make('content')
|
RichEditor::make('content')
|
||||||
->label(__('prescriptions.fields.content'))
|
->label(__('prescriptions.fields.content'))
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
|
->toolbarButtons([
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['highlightYellow', 'highlightRed'],
|
||||||
|
['bulletList', 'orderedList'],
|
||||||
|
['undo', 'redo'],
|
||||||
|
])
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|
@ -224,6 +232,13 @@ public static function form(Schema $schema): Schema
|
||||||
|
|
||||||
RichEditor::make('lab_content')
|
RichEditor::make('lab_content')
|
||||||
->label(__('prescriptions.fields.lab_content'))
|
->label(__('prescriptions.fields.lab_content'))
|
||||||
|
->plugins([HighlightColorPlugin::make()])
|
||||||
|
->toolbarButtons([
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['highlightYellow', 'highlightRed'],
|
||||||
|
['bulletList', 'orderedList'],
|
||||||
|
['undo', 'redo'],
|
||||||
|
])
|
||||||
->columnSpanFull(),
|
->columnSpanFull(),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,14 @@ class VisitResource extends Resource
|
||||||
|
|
||||||
protected static ?string $recordTitleAttribute = 'visit_date';
|
protected static ?string $recordTitleAttribute = 'visit_date';
|
||||||
|
|
||||||
|
public static function getRecordTitle(?\Illuminate\Database\Eloquent\Model $record): \Illuminate\Contracts\Support\Htmlable|string|null
|
||||||
|
{
|
||||||
|
if (! $record) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $record->patientRecord?->full_name ?? $record->visit_date ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getWidgets(): array
|
public static function getWidgets(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
@ -323,8 +331,14 @@ public static function table(Table $table): Table
|
||||||
->columns(2)
|
->columns(2)
|
||||||
->columnSpan(2)
|
->columnSpan(2)
|
||||||
->query(fn ($query, array $data) => $query
|
->query(fn ($query, array $data) => $query
|
||||||
->when($data['from_date'] ?? null, fn ($q) => $q->whereDate('visit_date', '>=', $data['from_date']))
|
->when($data['from_date'] ?? null, function ($q) use ($data) {
|
||||||
->when($data['to_date'] ?? null, fn ($q) => $q->whereDate('visit_date', '<=', $data['to_date']))
|
$fromDate = \Carbon\Carbon::parse($data['from_date'])->format('Y-m-d');
|
||||||
|
return $q->whereRaw("SUBSTRING(visit_date, 1, 10) >= ?", [$fromDate]);
|
||||||
|
})
|
||||||
|
->when($data['to_date'] ?? null, function ($q) use ($data) {
|
||||||
|
$toDate = \Carbon\Carbon::parse($data['to_date'])->format('Y-m-d');
|
||||||
|
return $q->whereRaw("SUBSTRING(visit_date, 1, 10) <= ?", [$toDate]);
|
||||||
|
})
|
||||||
)
|
)
|
||||||
->indicateUsing(function (array $data): array {
|
->indicateUsing(function (array $data): array {
|
||||||
$indicators = [];
|
$indicators = [];
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,31 @@ protected function getHeaderWidgets(): array
|
||||||
public function applyTableFilters(): void
|
public function applyTableFilters(): void
|
||||||
{
|
{
|
||||||
parent::applyTableFilters();
|
parent::applyTableFilters();
|
||||||
|
$this->applySortFromFilters();
|
||||||
$this->dispatchStatsUpdate();
|
$this->dispatchStatsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedTableFilters(): void
|
public function updatedTableFilters(): void
|
||||||
{
|
{
|
||||||
parent::updatedTableFilters();
|
parent::updatedTableFilters();
|
||||||
|
$this->applySortFromFilters();
|
||||||
$this->dispatchStatsUpdate();
|
$this->dispatchStatsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetTableFiltersForm(): void
|
public function resetTableFiltersForm(): void
|
||||||
{
|
{
|
||||||
parent::resetTableFiltersForm();
|
parent::resetTableFiltersForm();
|
||||||
|
$this->tableSort = null;
|
||||||
$this->dispatchStatsUpdate();
|
$this->dispatchStatsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function applySortFromFilters(): void
|
||||||
|
{
|
||||||
|
$filters = $this->tableFilters ?? [];
|
||||||
|
$fromDate = ($filters['visit_date_range']['from_date'] ?? null) ?: null;
|
||||||
|
|
||||||
|
$this->tableSort = $fromDate ? 'visit_date:asc' : null;
|
||||||
|
}
|
||||||
protected function dispatchStatsUpdate(): void
|
protected function dispatchStatsUpdate(): void
|
||||||
{
|
{
|
||||||
$filters = $this->tableFilters ?? [];
|
$filters = $this->tableFilters ?? [];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\RichEditor;
|
||||||
|
|
||||||
|
use Tiptap\Core\Mark;
|
||||||
|
use Tiptap\Utils\HTML;
|
||||||
|
|
||||||
|
class HighlightColorMark extends Mark
|
||||||
|
{
|
||||||
|
public static $name = 'highlightColor';
|
||||||
|
|
||||||
|
public function parseHTML(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'tag' => 'span',
|
||||||
|
'getAttrs' => function ($DOMNode) {
|
||||||
|
$color = $DOMNode->getAttribute('data-hbg');
|
||||||
|
if (! $color) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ['data-hbg' => $color];
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAttributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data-hbg' => [
|
||||||
|
'default' => null,
|
||||||
|
'parseHTML' => function ($DOMNode) {
|
||||||
|
return $DOMNode->getAttribute('data-hbg') ?: null;
|
||||||
|
},
|
||||||
|
'renderHTML' => function ($attributes) {
|
||||||
|
if (empty($attributes->{'data-hbg'})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$color = $attributes->{'data-hbg'};
|
||||||
|
return [
|
||||||
|
'data-hbg' => $color,
|
||||||
|
'style' => "background-color: {$color};",
|
||||||
|
];
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderHTML($mark, $HTMLAttributes = []): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'span',
|
||||||
|
HTML::mergeAttributes($HTMLAttributes),
|
||||||
|
0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\RichEditor;
|
||||||
|
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
|
||||||
|
use Filament\Forms\Components\RichEditor\RichEditorTool;
|
||||||
|
|
||||||
|
class HighlightColorPlugin implements RichContentPlugin
|
||||||
|
{
|
||||||
|
public static function make(): static
|
||||||
|
{
|
||||||
|
return app(static::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTipTapPhpExtensions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
app(HighlightColorMark::class),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTipTapJsExtensions(): array
|
||||||
|
{
|
||||||
|
return [asset('js/highlight-colors.js')];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditorTools(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
RichEditorTool::make('highlightYellow')
|
||||||
|
->label('برجسته زرد')
|
||||||
|
->icon('fi-o-highlight')
|
||||||
|
->activeJsExpression("editorUpdatedAt && \$getEditor()?.isActive('highlightColor', {'data-hbg': '#FFFF00'})")
|
||||||
|
->jsHandler("(function(){ var e=\$getEditor(); if(!e) return; e.isActive('highlightColor',{'data-hbg':'#FFFF00'}) ? e.chain().focus().unsetMark('highlightColor').run() : e.chain().focus().unsetMark('highlightColor').setMark('highlightColor',{'data-hbg':'#FFFF00'}).run(); })()"),
|
||||||
|
|
||||||
|
RichEditorTool::make('highlightRed')
|
||||||
|
->label('برجسته قرمز')
|
||||||
|
->icon('fi-o-highlight')
|
||||||
|
->extraAttributes(['style' => 'color: #ef4444'])
|
||||||
|
->activeJsExpression("editorUpdatedAt && \$getEditor()?.isActive('highlightColor', {'data-hbg': '#ef4444'})")
|
||||||
|
->jsHandler("(function(){ var e=\$getEditor(); if(!e) return; e.isActive('highlightColor',{'data-hbg':'#ef4444'}) ? e.chain().focus().unsetMark('highlightColor').run() : e.chain().focus().unsetMark('highlightColor').setMark('highlightColor',{'data-hbg':'#ef4444'}).run(); })()"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEditorActions(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,11 +48,13 @@ protected function buildQuery()
|
||||||
$query = Visit::query();
|
$query = Visit::query();
|
||||||
|
|
||||||
if ($this->fromDate) {
|
if ($this->fromDate) {
|
||||||
$query->whereDate('visit_date', '>=', $this->fromDate);
|
$fromDate = \Carbon\Carbon::parse($this->fromDate)->format('Y-m-d');
|
||||||
|
$query->whereRaw("SUBSTRING(visit_date, 1, 10) >= ?", [$fromDate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->toDate) {
|
if ($this->toDate) {
|
||||||
$query->whereDate('visit_date', '<=', $this->toDate);
|
$toDate = \Carbon\Carbon::parse($this->toDate)->format('Y-m-d');
|
||||||
|
$query->whereRaw("SUBSTRING(visit_date, 1, 10) <= ?", [$toDate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->doctor) {
|
if ($this->doctor) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ExpandableText extends Component
|
||||||
|
{
|
||||||
|
public string $html = '';
|
||||||
|
public int $limit = 200;
|
||||||
|
public bool $expanded = false;
|
||||||
|
|
||||||
|
public function toggle(): void
|
||||||
|
{
|
||||||
|
$this->expanded = ! $this->expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\View\View
|
||||||
|
{
|
||||||
|
return view('livewire.expandable-text');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Filament\Resources\PatientResource\Pages\EditPatient;
|
||||||
|
use App\Models\Patient;
|
||||||
|
use Filament\Forms\Components\FileUpload;
|
||||||
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
|
use Filament\Forms\Contracts\HasForms;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class PatientMediaPlayer extends Component implements HasForms
|
||||||
|
{
|
||||||
|
use InteractsWithForms;
|
||||||
|
|
||||||
|
public int $patientId;
|
||||||
|
public string $field;
|
||||||
|
public ?array $data = [];
|
||||||
|
|
||||||
|
public function mount(int $patientId, string $field): void
|
||||||
|
{
|
||||||
|
$this->patientId = $patientId;
|
||||||
|
$this->field = $field;
|
||||||
|
|
||||||
|
$patient = Patient::find($patientId);
|
||||||
|
|
||||||
|
$this->form->fill([
|
||||||
|
$field => EditPatient::normalizeFileList($patient?->{$field}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function form(Schema $form): Schema
|
||||||
|
{
|
||||||
|
$component = match ($this->field) {
|
||||||
|
'videos' => FileUpload::make('videos')
|
||||||
|
->hiddenLabel()
|
||||||
|
->multiple()
|
||||||
|
->disk('public')
|
||||||
|
->acceptedFileTypes([
|
||||||
|
'video/mp4', 'video/quicktime', 'video/x-msvideo',
|
||||||
|
'video/x-matroska', 'video/webm',
|
||||||
|
'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/x-wav',
|
||||||
|
'audio/ogg', 'audio/opus', 'audio/mp4', 'audio/x-m4a',
|
||||||
|
'audio/aac', 'audio/x-aac', 'audio/flac', 'audio/x-flac',
|
||||||
|
'audio/webm', 'audio/amr', 'audio/3gpp', 'audio/3gpp2',
|
||||||
|
'audio/aiff', 'audio/x-aiff',
|
||||||
|
])
|
||||||
|
->maxSize(102400)
|
||||||
|
->downloadable()
|
||||||
|
->openable()
|
||||||
|
->panelLayout('grid')
|
||||||
|
->extraAttributes(['class' => 'fi-video-gallery', 'data-hide-dropzone' => 'true'])
|
||||||
|
->disabled()
|
||||||
|
->deletable(false)
|
||||||
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
|
'audio_files' => FileUpload::make('audio_files')
|
||||||
|
->hiddenLabel()
|
||||||
|
->multiple()
|
||||||
|
->disk('public')
|
||||||
|
->acceptedFileTypes([
|
||||||
|
'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/x-wav',
|
||||||
|
'audio/ogg', 'audio/opus', 'audio/mp4', 'audio/x-m4a',
|
||||||
|
'audio/aac', 'audio/x-aac', 'audio/flac', 'audio/x-flac',
|
||||||
|
'audio/webm', 'audio/amr', 'audio/3gpp', 'audio/3gpp2',
|
||||||
|
'audio/aiff', 'audio/x-aiff',
|
||||||
|
])
|
||||||
|
->downloadable()
|
||||||
|
->openable()
|
||||||
|
->extraAttributes(['data-hide-dropzone' => 'true'])
|
||||||
|
->disabled()
|
||||||
|
->deletable(false)
|
||||||
|
->fetchFileInformation(false),
|
||||||
|
|
||||||
|
default => throw new \InvalidArgumentException("Unknown field: {$this->field}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return $form
|
||||||
|
->schema([$component])
|
||||||
|
->statePath('data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): \Illuminate\View\View
|
||||||
|
{
|
||||||
|
return view('livewire.patient-media-player');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,18 @@ class SettingsDropdown extends Component
|
||||||
public bool $syncFailed = false;
|
public bool $syncFailed = false;
|
||||||
public array $reportLines = [];
|
public array $reportLines = [];
|
||||||
|
|
||||||
|
public bool $peerConnected = false;
|
||||||
|
public ?string $peerPath = null;
|
||||||
|
public bool $peerChecked = false;
|
||||||
|
|
||||||
|
public function checkPeerStatus(): void
|
||||||
|
{
|
||||||
|
$result = SyncService::fromConfig()->ping();
|
||||||
|
|
||||||
|
$this->peerConnected = $result['connected'];
|
||||||
|
$this->peerPath = $result['path'];
|
||||||
|
$this->peerChecked = true;
|
||||||
|
}
|
||||||
public function runBackup(): void
|
public function runBackup(): void
|
||||||
{
|
{
|
||||||
$dbPath = database_path('database.sqlite');
|
$dbPath = database_path('database.sqlite');
|
||||||
|
|
@ -26,18 +38,62 @@ public function runBackup(): void
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupDir = storage_path('app/backups');
|
$timestamp = now()->format('Y-m-d_H-i-s');
|
||||||
|
$zipFilename = "matab-backup-{$timestamp}.zip";
|
||||||
|
$zipPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $zipFilename;
|
||||||
|
|
||||||
if (! is_dir($backupDir)) {
|
$zip = new \ZipArchive();
|
||||||
mkdir($backupDir, 0755, true);
|
if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||||
|
Notification::make()
|
||||||
|
->title(__('navigation.backup.failed'))
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (glob($backupDir . '/matab-backup-*.sqlite') as $oldFile) {
|
$noCompressExts = [
|
||||||
unlink($oldFile);
|
'jpg', 'jpeg', 'png', 'gif', 'webp',
|
||||||
|
'mp4', 'mov', 'avi', 'mkv', 'webm',
|
||||||
|
'mp3', 'wav', 'ogg', 'opus', 'm4a',
|
||||||
|
'zip', 'gz', 'rar', '7z',
|
||||||
|
];
|
||||||
|
|
||||||
|
$addFile = function (string $realPath, string $zipEntry) use ($zip, $noCompressExts): void {
|
||||||
|
$zip->addFile($realPath, $zipEntry);
|
||||||
|
$index = $zip->locateName($zipEntry);
|
||||||
|
$ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
|
||||||
|
if (in_array($ext, $noCompressExts, true)) {
|
||||||
|
$zip->setCompressionIndex($index, \ZipArchive::CM_STORE);
|
||||||
|
} else {
|
||||||
|
$zip->setCompressionIndex($index, \ZipArchive::CM_DEFLATE, 9);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$addFile($dbPath, 'database/database.sqlite');
|
||||||
|
|
||||||
|
$publicPath = storage_path('app/public');
|
||||||
|
foreach (['files', 'initials'] as $folder) {
|
||||||
|
$folderPath = $publicPath . '/' . $folder;
|
||||||
|
if (! is_dir($folderPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||||
|
);
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->isFile()) {
|
||||||
|
$relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen($folderPath) + 1));
|
||||||
|
$addFile($file->getPathname(), $folder . '/' . $relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = 'matab-backup-' . now()->format('Y-m-d_H-i-s') . '.sqlite';
|
$zip->close();
|
||||||
copy($dbPath, $backupDir . '/' . $filename);
|
|
||||||
|
session(['backup_tmp_path' => $zipPath, 'backup_tmp_name' => $zipFilename]);
|
||||||
|
|
||||||
|
$this->dispatch('backup-ready', url: route('backup.download'));
|
||||||
|
|
||||||
Notification::make()
|
Notification::make()
|
||||||
->title(__('navigation.backup.success'))
|
->title(__('navigation.backup.success'))
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Support\Enums\Alignment;
|
use Filament\Support\Enums\Alignment;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
|
@ -29,7 +30,18 @@ public function register(): void {}
|
||||||
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
if (app()->runningInConsole() === false && isset($_SERVER['HTTP_HOST'])) {
|
||||||
|
$scheme = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http';
|
||||||
|
$root = "{$scheme}://{$_SERVER['HTTP_HOST']}";
|
||||||
|
URL::forceRootUrl($root);
|
||||||
|
config([
|
||||||
|
'app.url' => $root,
|
||||||
|
'filesystems.disks.public.url' => $root . '/storage',
|
||||||
|
]);
|
||||||
|
}
|
||||||
Livewire::component('app.filament.widgets.visit-stats-widget', VisitStatsWidget::class);
|
Livewire::component('app.filament.widgets.visit-stats-widget', VisitStatsWidget::class);
|
||||||
|
Livewire::component('patient-media-player', \App\Livewire\PatientMediaPlayer::class);
|
||||||
|
Livewire::component('expandable-text', \App\Livewire\ExpandableText::class);
|
||||||
$observer = new SyncObserver();
|
$observer = new SyncObserver();
|
||||||
|
|
||||||
Patient::observe($observer);
|
Patient::observe($observer);
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,166 @@ public function panel(Panel $panel): Panel
|
||||||
->renderHook(
|
->renderHook(
|
||||||
PanelsRenderHook::BODY_END,
|
PanelsRenderHook::BODY_END,
|
||||||
fn (): string => <<<'HTML'
|
fn (): string => <<<'HTML'
|
||||||
|
<style>
|
||||||
|
#fi-ucg-backdrop{position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,.55);backdrop-filter:blur(3px)}
|
||||||
|
#fi-ucg-wrap{position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;padding:1rem}
|
||||||
|
#fi-ucg-card{background:#fff;border-radius:.75rem;box-shadow:0 20px 50px -10px rgba(0,0,0,.18);padding:1.5rem;max-width:26rem;width:100%;border:1px solid #fecaca}
|
||||||
|
:root.dark #fi-ucg-card{background:#1e2533;border-color:#7f1d1d}
|
||||||
|
#fi-ucg-icon{width:2.75rem;height:2.75rem;border-radius:50%;background:#fee2e2;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||||
|
:root.dark #fi-ucg-icon{background:rgba(127,29,15,.35)}
|
||||||
|
#fi-ucg-title{font-weight:700;font-size:.9375rem;color:#991b1b;margin:0 0 .35rem}
|
||||||
|
:root.dark #fi-ucg-title{color:#fca5a5}
|
||||||
|
#fi-ucg-desc{font-size:.875rem;color:#6b7280;line-height:1.6;margin:0}
|
||||||
|
:root.dark #fi-ucg-desc{color:#9ca3af}
|
||||||
|
#fi-ucg-footer{display:flex;gap:.625rem;justify-content:flex-start;margin-top:1.5rem;flex-wrap:wrap}
|
||||||
|
.fi-ucg-btn{display:inline-flex;align-items:center;justify-content:center;padding:.5rem 1rem;border-radius:.5rem;font-size:.875rem;font-weight:500;cursor:pointer;border:none;transition:background .15s,opacity .15s;outline:none}
|
||||||
|
.fi-ucg-btn-cancel{background:#f3f4f6;color:#374151;border:1px solid #d1d5db}
|
||||||
|
.fi-ucg-btn-cancel:hover{background:#e5e7eb}
|
||||||
|
:root.dark .fi-ucg-btn-cancel{background:#374151;color:#d1d5db;border-color:#4b5563}
|
||||||
|
:root.dark .fi-ucg-btn-cancel:hover{background:#4b5563}
|
||||||
|
.fi-ucg-btn-discard{background:#dc2626;color:#fff}
|
||||||
|
.fi-ucg-btn-discard:hover{background:#b91c1c}
|
||||||
|
:root.dark .fi-ucg-btn-discard{background:#dc2626}
|
||||||
|
:root.dark .fi-ucg-btn-discard:hover{background:#b91c1c}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* Unsaved-changes guard — runs synchronously before Livewire @script */
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
/* ── i18n ── */
|
||||||
|
function texts() {
|
||||||
|
var fa = document.documentElement.lang === 'fa';
|
||||||
|
return fa ? {
|
||||||
|
dir:'rtl', title:'تغییرات ذخیره نشده',
|
||||||
|
desc:'اطلاعات وارد شده هنوز ذخیره نشدهاند.<br>در صورت خروج، تمام تغییرات از دست خواهد رفت.',
|
||||||
|
cancel:'بازگشت به ویرایش', discard:'خروج بدون ذخیره',
|
||||||
|
} : {
|
||||||
|
dir:'ltr', title:'Unsaved Changes',
|
||||||
|
desc:'You have unsaved changes.<br>Leaving now will permanently discard them.',
|
||||||
|
cancel:'Keep Editing', discard:'Leave Without Saving',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── dirty-state helper ── */
|
||||||
|
/* savedDataHash is #[Locked] in Livewire — never try to set it from JS.
|
||||||
|
Instead use a bypass flag so the next navigate event is skipped. */
|
||||||
|
var bypassNextNavigate = false;
|
||||||
|
|
||||||
|
function isDirty(wire) {
|
||||||
|
try {
|
||||||
|
if (!wire || wire?.__instance?.effects?.redirect) return false;
|
||||||
|
return window.jsMd5(JSON.stringify(wire.data).replace(/\\/g, '')) !== wire.savedDataHash;
|
||||||
|
} catch (_) { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── registered wires (for beforeunload) ── */
|
||||||
|
var registeredWires = [];
|
||||||
|
function registerWire(w) { if (!registeredWires.includes(w)) registeredWires.push(w); }
|
||||||
|
document.addEventListener('livewire:navigated', function () {
|
||||||
|
bypassNextNavigate = false;
|
||||||
|
registeredWires = registeredWires.filter(function (w) {
|
||||||
|
try { return typeof w.savedDataHash !== 'undefined'; } catch (_) { return false; }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Alpine component (registered before Alpine starts) ── */
|
||||||
|
document.addEventListener('alpine:init', function () {
|
||||||
|
Alpine.data('fiUnsavedGuard', function () {
|
||||||
|
return {
|
||||||
|
show:false, pendingUrl:null, pendingWire:null,
|
||||||
|
pendingIsSpa:false, navigating:false, t:texts(),
|
||||||
|
init() {
|
||||||
|
window.$fiUnsavedGuard = this;
|
||||||
|
document.addEventListener('livewire:navigated', () => {
|
||||||
|
this.navigating = false;
|
||||||
|
this.t = texts();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
open(url, wire, isSpa) {
|
||||||
|
this.pendingUrl=url; this.pendingWire=wire;
|
||||||
|
this.pendingIsSpa=!!isSpa; this.show=true;
|
||||||
|
},
|
||||||
|
cancel() { this.show=false; this.pendingUrl=null; this.pendingWire=null; },
|
||||||
|
discard() {
|
||||||
|
var url=this.pendingUrl;
|
||||||
|
this.show=false; this.pendingUrl=null; this.pendingWire=null;
|
||||||
|
this.navigating=true;
|
||||||
|
if (!url) { history.back(); return; }
|
||||||
|
window.location.href = url;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── override Filament's hooks (must happen before @script runs) ── */
|
||||||
|
window.setUpSpaModeUnsavedDataChangesAlert = function ({ resolveLivewireComponentUsing, $wire }) {
|
||||||
|
registerWire($wire);
|
||||||
|
document.addEventListener('livewire:navigate', function (event) {
|
||||||
|
if (bypassNextNavigate) { return; } /* reset is handled by livewire:navigated so all listeners see the flag */
|
||||||
|
if (typeof resolveLivewireComponentUsing() === 'undefined') return;
|
||||||
|
if (!isDirty($wire)) return;
|
||||||
|
event.preventDefault();
|
||||||
|
var url = event.detail?.url;
|
||||||
|
window.$fiUnsavedGuard?.open(url ? url.toString() : null, $wire, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.setUpUnsavedDataChangesAlert = function ({ $wire }) {
|
||||||
|
registerWire($wire);
|
||||||
|
document.addEventListener('click', function (e) {
|
||||||
|
var link = e.target.closest('a[href]:not([target="_blank"]):not([href^="javascript"]):not([href^="#"])');
|
||||||
|
if (!link) return;
|
||||||
|
var href = link.getAttribute('href');
|
||||||
|
if (!href || href === window.location.pathname) return;
|
||||||
|
if (!isDirty($wire)) return;
|
||||||
|
e.preventDefault(); e.stopPropagation();
|
||||||
|
window.$fiUnsavedGuard?.open(link.href, $wire, false);
|
||||||
|
}, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── beforeunload: only for real tab/browser close ── */
|
||||||
|
window.addEventListener('beforeunload', function (event) {
|
||||||
|
if (window.$fiUnsavedGuard?.navigating) return;
|
||||||
|
for (var i = 0; i < registeredWires.length; i++) {
|
||||||
|
if (isDirty(registeredWires[i])) { event.preventDefault(); event.returnValue = true; return; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="fiUnsavedGuard"
|
||||||
|
x-cloak
|
||||||
|
@keydown.escape.window="if (show) cancel()"
|
||||||
|
>
|
||||||
|
<template x-if="show">
|
||||||
|
<div>
|
||||||
|
<div id="fi-ucg-backdrop" @click="cancel()"></div>
|
||||||
|
<div id="fi-ucg-wrap">
|
||||||
|
<div id="fi-ucg-card" :dir="t.dir" role="dialog" aria-modal="true">
|
||||||
|
<div style="display:flex;align-items:flex-start;gap:.875rem">
|
||||||
|
<div id="fi-ucg-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.75" stroke="#dc2626" style="width:1.25rem;height:1.25rem">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p id="fi-ucg-title" x-text="t.title"></p>
|
||||||
|
<p id="fi-ucg-desc" x-html="t.desc"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="fi-ucg-footer">
|
||||||
|
<button @click="cancel()" type="button" class="fi-ucg-btn fi-ucg-btn-cancel" x-text="t.cancel"></button>
|
||||||
|
<button @click="discard()" type="button" class="fi-ucg-btn fi-ucg-btn-discard" x-text="t.discard"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var speeds = [0.5, 1, 1.5, 2];
|
var speeds = [0.5, 1, 1.5, 2];
|
||||||
|
|
@ -100,6 +260,50 @@ public function panel(Panel $panel): Panel
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
/* Fix FilePond gallery: remove inline styles set by filepond JS that conflict with our CSS */
|
||||||
|
function fixGalleryItems() {
|
||||||
|
document.querySelectorAll('.fi-photo-gallery .filepond--item').forEach(function (item) {
|
||||||
|
item.style.removeProperty('width');
|
||||||
|
item.style.removeProperty('height');
|
||||||
|
item.style.removeProperty('transform');
|
||||||
|
item.style.removeProperty('left');
|
||||||
|
item.style.removeProperty('top');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.fi-photo-gallery .filepond--image-preview-wrapper').forEach(function (w) {
|
||||||
|
w.style.removeProperty('transform');
|
||||||
|
w.style.removeProperty('margin-top');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.fi-photo-gallery .filepond--list-scroller').forEach(function (el) {
|
||||||
|
el.style.removeProperty('height');
|
||||||
|
el.style.removeProperty('top');
|
||||||
|
el.style.removeProperty('transform');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.fi-photo-gallery .filepond--root').forEach(function (root) {
|
||||||
|
root.style.removeProperty('height');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var _fixTimer = null;
|
||||||
|
var _observer = new MutationObserver(function (mutations) {
|
||||||
|
if (_fixTimer !== null) return;
|
||||||
|
var relevant = mutations.some(function (m) {
|
||||||
|
return m.target && m.target.closest &&
|
||||||
|
m.target.closest('.fi-photo-gallery');
|
||||||
|
});
|
||||||
|
if (!relevant) return;
|
||||||
|
_fixTimer = requestAnimationFrame(function () {
|
||||||
|
_fixTimer = null;
|
||||||
|
fixGalleryItems();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['style'] });
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', fixGalleryItems);
|
||||||
|
document.addEventListener('livewire:navigated', fixGalleryItems);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
HTML,
|
HTML,
|
||||||
)
|
)
|
||||||
->renderHook(
|
->renderHook(
|
||||||
|
|
@ -108,6 +312,15 @@ public function panel(Panel $panel): Panel
|
||||||
<div id="fi-lightbox">
|
<div id="fi-lightbox">
|
||||||
<div id="fi-lb-wrap">
|
<div id="fi-lb-wrap">
|
||||||
<img id="fi-lb-img" src="" alt="">
|
<img id="fi-lb-img" src="" alt="">
|
||||||
|
<div id="fi-lb-caption" style="display:none">
|
||||||
|
<div id="fi-lb-cap-header">
|
||||||
|
<div id="fi-lb-cap-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="rgba(20,30,60,0.85)" stroke-width="1.5" style="width:14px;height:14px;"><path stroke-linecap="round" stroke-linejoin="round" d="M7 8h6M7 11h4m1 7l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v5a2 2 0 01-2 2h-2l-3 3z"/></svg></div>
|
||||||
|
<span id="fi-lb-cap-title">یادداشت پزشک</span>
|
||||||
|
<button id="fi-lb-cap-close" title="پنهان کردن">✕</button>
|
||||||
|
</div>
|
||||||
|
<div id="fi-lb-cap-body"></div>
|
||||||
|
</div>
|
||||||
|
<button id="fi-lb-cap-toggle" title="نمایش یادداشت"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" style="width:20px;height:20px;"><path stroke-linecap="round" stroke-linejoin="round" d="M7 8h6M7 11h4m1 7l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v5a2 2 0 01-2 2h-2l-3 3z"/></svg></button>
|
||||||
<button id="fi-lb-close" title="بستن">✕</button>
|
<button id="fi-lb-close" title="بستن">✕</button>
|
||||||
<button id="fi-lb-prev" title="قبلی">‹</button>
|
<button id="fi-lb-prev" title="قبلی">‹</button>
|
||||||
<button id="fi-lb-next" title="بعدی">›</button>
|
<button id="fi-lb-next" title="بعدی">›</button>
|
||||||
|
|
@ -119,29 +332,82 @@ public function panel(Panel $panel): Panel
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var lb = document.getElementById('fi-lightbox');
|
var lb = document.getElementById('fi-lightbox');
|
||||||
var lbWrap = document.getElementById('fi-lb-wrap');
|
var lbWrap = document.getElementById('fi-lb-wrap');
|
||||||
var lbImg = document.getElementById('fi-lb-img');
|
var lbImg = document.getElementById('fi-lb-img');
|
||||||
var lbCnt = document.getElementById('fi-lb-counter');
|
var lbCnt = document.getElementById('fi-lb-counter');
|
||||||
var lbPrev = document.getElementById('fi-lb-prev');
|
var lbPrev = document.getElementById('fi-lb-prev');
|
||||||
var lbNext = document.getElementById('fi-lb-next');
|
var lbNext = document.getElementById('fi-lb-next');
|
||||||
var lbClose = document.getElementById('fi-lb-close');
|
var lbClose = document.getElementById('fi-lb-close');
|
||||||
|
var lbCaption = document.getElementById('fi-lb-caption');
|
||||||
|
var lbCapBody = document.getElementById('fi-lb-cap-body');
|
||||||
|
var lbCapClose = document.getElementById('fi-lb-cap-close');
|
||||||
|
var lbCapToggle = document.getElementById('fi-lb-cap-toggle');
|
||||||
|
var lbCapTitleEl = document.getElementById('fi-lb-cap-title');
|
||||||
if (!lb) return;
|
if (!lb) return;
|
||||||
|
|
||||||
var images = [], currentIdx = 0, zoom = 1;
|
var images = [], captions = [], sections = [], currentIdx = 0, zoom = 1;
|
||||||
var isPanning = false, panStartX = 0, panStartY = 0, panX = 0, panY = 0;
|
var isPanning = false, panStartX = 0, panStartY = 0, panX = 0, panY = 0;
|
||||||
|
var manuallyHidden = false, autoHidden = false;
|
||||||
|
|
||||||
|
var capTitles = {
|
||||||
|
before: { fa: 'توضیحات قبل از عمل', en: 'Pre-Op Notes' },
|
||||||
|
after: { fa: 'توضیحات بعد از عمل', en: 'Post-Op Notes' },
|
||||||
|
default: { fa: 'یادداشت پزشک', en: 'Doctor\'s Notes' },
|
||||||
|
};
|
||||||
|
function updateTitle() {
|
||||||
|
if (!lbCapTitleEl) return;
|
||||||
|
var sec = sections[currentIdx] || 'default';
|
||||||
|
var locale = document.documentElement.lang === 'fa' ? 'fa' : 'en';
|
||||||
|
lbCapTitleEl.textContent = (capTitles[sec] || capTitles['default'])[locale];
|
||||||
|
}
|
||||||
|
|
||||||
function applyTransform() {
|
function applyTransform() {
|
||||||
lbImg.style.transform = 'scale(' + zoom + ') translate(' + panX + 'px, ' + panY + 'px)';
|
lbImg.style.transform = 'scale(' + zoom + ') translate(' + panX + 'px, ' + panY + 'px)';
|
||||||
lbImg.classList.toggle('lb-zoomed', zoom > 1);
|
lbImg.classList.toggle('lb-zoomed', zoom > 1);
|
||||||
|
var hasCaption = lbCaption && lbCaption.style.display !== 'none';
|
||||||
|
if (hasCaption && zoom > 1 && !lbCaption.classList.contains('lb-cap-hidden')) {
|
||||||
|
lbCaption.classList.add('lb-cap-hidden');
|
||||||
|
lb.classList.add('lb-cap-collapsed');
|
||||||
|
if (lbCapToggle) lbCapToggle.classList.add('lb-cap-toggle-visible');
|
||||||
|
autoHidden = true;
|
||||||
|
} else if (zoom <= 1 && autoHidden && !manuallyHidden) {
|
||||||
|
lbCaption.classList.remove('lb-cap-hidden');
|
||||||
|
lb.classList.remove('lb-cap-collapsed');
|
||||||
|
if (lbCapToggle) lbCapToggle.classList.remove('lb-cap-toggle-visible');
|
||||||
|
autoHidden = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLightbox(imgs, idx) {
|
function updateCaption() {
|
||||||
|
var cap = captions[currentIdx] || '';
|
||||||
|
if (cap) {
|
||||||
|
if (lbCapBody) lbCapBody.innerHTML = cap;
|
||||||
|
lbCaption.style.display = '';
|
||||||
|
lb.classList.remove('lb-no-caption');
|
||||||
|
/* Start open by default */
|
||||||
|
lbCaption.classList.remove('lb-cap-hidden');
|
||||||
|
lb.classList.remove('lb-cap-collapsed');
|
||||||
|
if (lbCapToggle) lbCapToggle.classList.remove('lb-cap-toggle-visible');
|
||||||
|
} else {
|
||||||
|
lbCaption.style.display = 'none';
|
||||||
|
if (lbCapBody) lbCapBody.innerHTML = '';
|
||||||
|
lb.classList.add('lb-no-caption');
|
||||||
|
if (lbCapToggle) lbCapToggle.classList.remove('lb-cap-toggle-visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLightbox(imgs, idx, caps, secs) {
|
||||||
images = imgs;
|
images = imgs;
|
||||||
|
captions = caps || [];
|
||||||
|
sections = secs || [];
|
||||||
currentIdx = idx;
|
currentIdx = idx;
|
||||||
zoom = 1; panX = 0; panY = 0;
|
zoom = 1; panX = 0; panY = 0;
|
||||||
|
manuallyHidden = false; autoHidden = false;
|
||||||
applyTransform();
|
applyTransform();
|
||||||
updateCounter();
|
updateCounter();
|
||||||
|
updateCaption();
|
||||||
|
updateTitle();
|
||||||
loadImage();
|
loadImage();
|
||||||
lb.classList.add('lb-open');
|
lb.classList.add('lb-open');
|
||||||
document.addEventListener('keydown', onKey);
|
document.addEventListener('keydown', onKey);
|
||||||
|
|
@ -160,6 +426,8 @@ function navigate(dir) {
|
||||||
applyTransform();
|
applyTransform();
|
||||||
loadImage();
|
loadImage();
|
||||||
updateCounter();
|
updateCounter();
|
||||||
|
updateCaption();
|
||||||
|
updateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadImage() {
|
function loadImage() {
|
||||||
|
|
@ -220,6 +488,18 @@ function onKey(e) {
|
||||||
lbClose.addEventListener('click', hideLightbox);
|
lbClose.addEventListener('click', hideLightbox);
|
||||||
lbPrev.addEventListener('click', function () { navigate(-1); });
|
lbPrev.addEventListener('click', function () { navigate(-1); });
|
||||||
lbNext.addEventListener('click', function () { navigate(1); });
|
lbNext.addEventListener('click', function () { navigate(1); });
|
||||||
|
if (lbCapClose) lbCapClose.addEventListener('click', function () {
|
||||||
|
manuallyHidden = true;
|
||||||
|
lbCaption.classList.add('lb-cap-hidden');
|
||||||
|
lb.classList.add('lb-cap-collapsed');
|
||||||
|
if (lbCapToggle) lbCapToggle.classList.add('lb-cap-toggle-visible');
|
||||||
|
});
|
||||||
|
if (lbCapToggle) lbCapToggle.addEventListener('click', function () {
|
||||||
|
manuallyHidden = false; autoHidden = false;
|
||||||
|
lbCaption.classList.remove('lb-cap-hidden');
|
||||||
|
lb.classList.remove('lb-cap-collapsed');
|
||||||
|
lbCapToggle.classList.remove('lb-cap-toggle-visible');
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
var thumb = e.target.closest('[data-lb-src]');
|
var thumb = e.target.closest('[data-lb-src]');
|
||||||
|
|
@ -230,8 +510,10 @@ function onKey(e) {
|
||||||
? Array.from(document.querySelectorAll('[data-lb-group="' + group + '"]'))
|
? Array.from(document.querySelectorAll('[data-lb-group="' + group + '"]'))
|
||||||
: [thumb];
|
: [thumb];
|
||||||
var urls = all.map(function (el) { return el.getAttribute('data-lb-src'); });
|
var urls = all.map(function (el) { return el.getAttribute('data-lb-src'); });
|
||||||
|
var caps = all.map(function (el) { return el.getAttribute('data-lb-caption') || ''; });
|
||||||
|
var secs = all.map(function (el) { return el.getAttribute('data-lb-section') || ''; });
|
||||||
var idx = all.indexOf(thumb);
|
var idx = all.indexOf(thumb);
|
||||||
showLightbox(urls, Math.max(0, idx));
|
showLightbox(urls, Math.max(0, idx), caps, secs);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
|
|
@ -248,6 +530,13 @@ function onKey(e) {
|
||||||
var allItems = Array.from(root.querySelectorAll('.filepond--item'));
|
var allItems = Array.from(root.querySelectorAll('.filepond--item'));
|
||||||
var clickedIdx = allItems.indexOf(item);
|
var clickedIdx = allItems.indexOf(item);
|
||||||
|
|
||||||
|
var capWrapper = root.closest('[data-lb-section]');
|
||||||
|
var lbSec = capWrapper ? capWrapper.getAttribute('data-lb-section') : '';
|
||||||
|
var lbCap = capWrapper ? capWrapper.getAttribute('data-lb-caption') : '';
|
||||||
|
function mkArrays(urls) {
|
||||||
|
return { caps: urls.map(function() { return lbCap; }), secs: urls.map(function() { return lbSec; }) };
|
||||||
|
}
|
||||||
|
|
||||||
var pond = null;
|
var pond = null;
|
||||||
var alpineEl = root.closest('[x-data]');
|
var alpineEl = root.closest('[x-data]');
|
||||||
if (alpineEl && window.Alpine) {
|
if (alpineEl && window.Alpine) {
|
||||||
|
|
@ -269,7 +558,7 @@ function onKey(e) {
|
||||||
urls1.push(sid.startsWith('http') || sid.startsWith('/') ? sid : '/storage/' + sid);
|
urls1.push(sid.startsWith('http') || sid.startsWith('/') ? sid : '/storage/' + sid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (urls1.length) { showLightbox(urls1, Math.min(clickedIdx, urls1.length - 1)); return; }
|
if (urls1.length) { var a1=mkArrays(urls1); showLightbox(urls1, Math.min(clickedIdx, urls1.length - 1), a1.caps, a1.secs); return; }
|
||||||
} catch (e2) {}
|
} catch (e2) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +573,8 @@ function onKey(e) {
|
||||||
if (files) {
|
if (files) {
|
||||||
var arr = Array.isArray(files) ? files : Object.values(files);
|
var arr = Array.isArray(files) ? files : Object.values(files);
|
||||||
if (arr.length) {
|
if (arr.length) {
|
||||||
showLightbox(arr.map(function (f) { return '/storage/' + f; }), Math.min(clickedIdx, arr.length - 1));
|
var arr2=arr.map(function(f){return '/storage/'+f;}); var a2=mkArrays(arr2);
|
||||||
|
showLightbox(arr2, Math.min(clickedIdx, arr2.length-1), a2.caps, a2.secs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +592,7 @@ function onKey(e) {
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (imgs.length) showLightbox(imgs, fbIdx);
|
if (imgs.length) { var a3=mkArrays(imgs); showLightbox(imgs, fbIdx, a3.caps, a3.secs); }
|
||||||
}, true);
|
}, true);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -312,7 +602,11 @@ function onKey(e) {
|
||||||
Action::make('switch_language')
|
Action::make('switch_language')
|
||||||
->label(fn () => app()->getLocale() === 'fa' ? 'English' : 'فارسی')
|
->label(fn () => app()->getLocale() === 'fa' ? 'English' : 'فارسی')
|
||||||
->icon('heroicon-o-language')
|
->icon('heroicon-o-language')
|
||||||
->url(fn () => route('locale.switch', app()->getLocale() === 'fa' ? 'en' : 'fa')),
|
->url(fn () => route('locale.switch', app()->getLocale() === 'fa' ? 'en' : 'fa'))
|
||||||
|
->openUrlInNewTab(false)
|
||||||
|
->extraAttributes(fn () => [
|
||||||
|
'x-on:click.prevent' => 'window.location.href = \'' . route('locale.switch', app()->getLocale() === 'fa' ? 'en' : 'fa') . '\'',
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
->navigationGroups([
|
->navigationGroups([
|
||||||
NavigationGroup::make('بیماران و ویزیت'),
|
NavigationGroup::make('بیماران و ویزیت'),
|
||||||
|
|
@ -321,6 +615,8 @@ function onKey(e) {
|
||||||
NavigationGroup::make('تنظیمات'),
|
NavigationGroup::make('تنظیمات'),
|
||||||
])
|
])
|
||||||
->sidebarFullyCollapsibleOnDesktop()
|
->sidebarFullyCollapsibleOnDesktop()
|
||||||
|
->spa()
|
||||||
|
->unsavedChangesAlerts()
|
||||||
->maxContentWidth(Width::Full)
|
->maxContentWidth(Width::Full)
|
||||||
->viteTheme('resources/css/filament/admin/theme.css')
|
->viteTheme('resources/css/filament/admin/theme.css')
|
||||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,38 @@ public static function fromConfig(): static
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ping(): array
|
||||||
|
{
|
||||||
|
if (! $this->isConfigured()) {
|
||||||
|
return ['connected' => false, 'path' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = escapeshellarg($this->peerIp);
|
||||||
|
$cmd = PHP_OS_FAMILY === 'Windows'
|
||||||
|
? "ping -n 1 -w 1000 {$ip}"
|
||||||
|
: "ping -c 1 -W 1 {$ip}";
|
||||||
|
|
||||||
|
exec($cmd . ' 2>&1', $output, $exitCode);
|
||||||
|
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
return ['connected' => false, 'path' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = null;
|
||||||
|
try {
|
||||||
|
$response = Http::timeout(3)
|
||||||
|
->withHeader('X-Sync-Token', $this->token)
|
||||||
|
->get("http://{$this->peerIp}:{$this->peerPort}/api/sync/ping");
|
||||||
|
|
||||||
|
if ($response->successful() && $response->json('status') === 'ok') {
|
||||||
|
$path = $response->json('path');
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['connected' => true, 'path' => $path];
|
||||||
|
}
|
||||||
|
|
||||||
public function isConfigured(): bool
|
public function isConfigured(): bool
|
||||||
{
|
{
|
||||||
return $this->peerIp !== '' && $this->token !== '';
|
return $this->peerIp !== '' && $this->token !== '';
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->trustHosts(at: fn () => ['.*'], subdomains: false);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { build } from 'vite'
|
||||||
|
|
||||||
|
await build({
|
||||||
|
configFile: false,
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: './resources/js/highlight-colors.js',
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: () => 'highlight-colors.js',
|
||||||
|
},
|
||||||
|
outDir: './public/js',
|
||||||
|
emptyOutDir: false,
|
||||||
|
minify: true,
|
||||||
|
},
|
||||||
|
logLevel: 'warn',
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('highlight-colors.js built successfully → public/js/highlight-colors.js')
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"ariaieboy/filament-jalali": "2.0",
|
"ariaieboy/filament-jalali": "2.0",
|
||||||
|
"awcodes/richer-editor": "^1.2",
|
||||||
"barryvdh/laravel-dompdf": "^3.1",
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"bezhansalleh/filament-shield": "^4.1",
|
"bezhansalleh/filament-shield": "^4.1",
|
||||||
"filament/filament": "^4.0",
|
"filament/filament": "^4.0",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ab5f5cc9631f7f46d10ef65adf2fcbda",
|
"content-hash": "2016fafdbb8bbad0a40726291171c947",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
|
|
@ -217,6 +217,91 @@
|
||||||
],
|
],
|
||||||
"time": "2025-02-21T12:23:08+00:00"
|
"time": "2025-02-21T12:23:08+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "awcodes/richer-editor",
|
||||||
|
"version": "v1.2.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/awcodes/richer-editor.git",
|
||||||
|
"reference": "2e1dadfc6f582963fb2d9539890c06874aba1e6e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/awcodes/richer-editor/zipball/2e1dadfc6f582963fb2d9539890c06874aba1e6e",
|
||||||
|
"reference": "2e1dadfc6f582963fb2d9539890c06874aba1e6e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"filament/forms": "^4.3",
|
||||||
|
"league/html-to-markdown": "*",
|
||||||
|
"phiki/phiki": "^2.0",
|
||||||
|
"php": "^8.2",
|
||||||
|
"spatie/laravel-package-tools": "^1.15.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"larastan/larastan": "^3.0",
|
||||||
|
"laravel/pint": "^1.0",
|
||||||
|
"nunomaduro/collision": "^8.0",
|
||||||
|
"orchestra/testbench": "^9.0|^10.0",
|
||||||
|
"pestphp/pest": "^3.0",
|
||||||
|
"pestphp/pest-plugin-arch": "^3.0",
|
||||||
|
"pestphp/pest-plugin-laravel": "^3.0",
|
||||||
|
"pestphp/pest-plugin-livewire": "^3.0",
|
||||||
|
"phpstan/extension-installer": "^1.1",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^2.0",
|
||||||
|
"rector/rector": "^2.0",
|
||||||
|
"spatie/laravel-ray": "^1.26"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"aliases": {
|
||||||
|
"RicherEditor": "Awcodes\\RicherEditor\\Facades\\RicherEditor"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Awcodes\\RicherEditor\\RicherEditorServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Awcodes\\RicherEditor\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Adam Weston",
|
||||||
|
"email": "awcodes1@gmail.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A collection of extensions and tools to enhance the Filament Rich Editor field.",
|
||||||
|
"homepage": "https://github.com/awcodes/richer-editor",
|
||||||
|
"keywords": [
|
||||||
|
"awcodes",
|
||||||
|
"filament",
|
||||||
|
"filamentphp",
|
||||||
|
"laravel",
|
||||||
|
"richer-editor"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/awcodes/richer-editor/issues",
|
||||||
|
"source": "https://github.com/awcodes/richer-editor"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/awcodes",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-13T16:57:56+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "barryvdh/laravel-dompdf",
|
"name": "barryvdh/laravel-dompdf",
|
||||||
"version": "v3.1.1",
|
"version": "v3.1.1",
|
||||||
|
|
@ -3520,6 +3605,95 @@
|
||||||
},
|
},
|
||||||
"time": "2026-01-23T15:30:45+00:00"
|
"time": "2026-01-23T15:30:45+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/html-to-markdown",
|
||||||
|
"version": "5.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/html-to-markdown.git",
|
||||||
|
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||||
|
"reference": "0b4066eede55c48f38bcee4fb8f0aa85654390fd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-xml": "*",
|
||||||
|
"php": "^7.2.5 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mikehaertl/php-shellcommand": "^1.1.0",
|
||||||
|
"phpstan/phpstan": "^1.8.8",
|
||||||
|
"phpunit/phpunit": "^8.5 || ^9.2",
|
||||||
|
"scrutinizer/ocular": "^1.6",
|
||||||
|
"unleashedtech/php-coding-standard": "^2.7 || ^3.0",
|
||||||
|
"vimeo/psalm": "^4.22 || ^5.0"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/html-to-markdown"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.2-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\HTMLToMarkdown\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Colin O'Dell",
|
||||||
|
"email": "colinodell@gmail.com",
|
||||||
|
"homepage": "https://www.colinodell.com",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nick Cernis",
|
||||||
|
"email": "nick@cern.is",
|
||||||
|
"homepage": "http://modernnerd.net",
|
||||||
|
"role": "Original Author"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An HTML-to-markdown conversion helper for PHP",
|
||||||
|
"homepage": "https://github.com/thephpleague/html-to-markdown",
|
||||||
|
"keywords": [
|
||||||
|
"html",
|
||||||
|
"markdown"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/html-to-markdown/issues",
|
||||||
|
"source": "https://github.com/thephpleague/html-to-markdown/tree/5.1.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://www.colinodell.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/colinodell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/league/html-to-markdown",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-07-12T21:21:09+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "league/mime-type-detection",
|
"name": "league/mime-type-detection",
|
||||||
"version": "1.16.0",
|
"version": "1.16.0",
|
||||||
|
|
@ -5251,6 +5425,80 @@
|
||||||
},
|
},
|
||||||
"time": "2020-10-15T08:29:30+00:00"
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phiki/phiki",
|
||||||
|
"version": "v2.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phikiphp/phiki.git",
|
||||||
|
"reference": "24375dbc474dbc484de7d53c6bab0bb9e198f647"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phikiphp/phiki/zipball/24375dbc474dbc484de7d53c6bab0bb9e198f647",
|
||||||
|
"reference": "24375dbc474dbc484de7d53c6bab0bb9e198f647",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^8.2",
|
||||||
|
"psr/simple-cache": "^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/support": "^11.45",
|
||||||
|
"laravel/pint": "^1.18.1",
|
||||||
|
"league/commonmark": "^2.5.3",
|
||||||
|
"orchestra/testbench": "^9.15",
|
||||||
|
"pestphp/pest": "^3.5.1",
|
||||||
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
|
"phpstan/phpstan": "^2.0",
|
||||||
|
"symfony/var-dumper": "^7.1.6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"league/commonmark": "Required to use the CommonMark adapter (^2.5.3)"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Phiki\\Adapters\\Laravel\\PhikiServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Phiki\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ryan Chandler",
|
||||||
|
"email": "support@ryangjchandler.co.uk",
|
||||||
|
"homepage": "https://ryangjchandler.co.uk",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Syntax highlighting using TextMate grammars in PHP.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phikiphp/phiki/issues",
|
||||||
|
"source": "https://github.com/phikiphp/phiki/tree/v2.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/ryangjchandler",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://buymeacoffee.com/ryangjchandler",
|
||||||
|
"type": "other"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-01T15:56:08+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.9.5",
|
"version": "1.9.5",
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@
|
||||||
'last_sync' => 'Last Sync',
|
'last_sync' => 'Last Sync',
|
||||||
'pending_changes' => 'Pending Changes',
|
'pending_changes' => 'Pending Changes',
|
||||||
'peer_address' => 'Peer System Address',
|
'peer_address' => 'Peer System Address',
|
||||||
|
'peer_connected' => 'Connected',
|
||||||
|
'peer_disconnected' => 'Disconnected',
|
||||||
|
'peer_checking' => 'Checking...',
|
||||||
'never' => 'Never',
|
'never' => 'Never',
|
||||||
'setup_guide' => 'Setup Guide',
|
'setup_guide' => 'Setup Guide',
|
||||||
'setup_description' => 'To enable synchronization, set the following values in the .env file on both systems:',
|
'setup_description' => 'To enable synchronization, set the following values in the .env file on both systems:',
|
||||||
|
|
@ -115,9 +118,13 @@
|
||||||
'close' => 'Close',
|
'close' => 'Close',
|
||||||
],
|
],
|
||||||
'backup' => [
|
'backup' => [
|
||||||
'label' => 'Backup',
|
'label' => 'Backup',
|
||||||
'success' => 'Backup completed successfully',
|
'success' => 'Backup completed successfully',
|
||||||
'failed' => 'Backup failed: database file not found',
|
'failed' => 'Backup failed: database file not found',
|
||||||
|
'status_preparing' => 'Preparing backup...',
|
||||||
|
'status_compressing' => 'Compressing files...',
|
||||||
|
'status_ready' => 'Backup file is ready ✓',
|
||||||
|
'download_button' => 'Download Backup',
|
||||||
],
|
],
|
||||||
'import' => [
|
'import' => [
|
||||||
'label' => 'Import from Database',
|
'label' => 'Import from Database',
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
'illness' => '2. Medical History',
|
'illness' => '2. Medical History',
|
||||||
'surgery_notes' => '3. Surgery Notes',
|
'surgery_notes' => '3. Surgery Notes',
|
||||||
'media' => '4. Patient Media',
|
'media' => '4. Patient Media',
|
||||||
|
'record_info' => 'Record Info',
|
||||||
],
|
],
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'icno' => 'National ID',
|
'icno' => 'National ID',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
'treatment_type' => 'Treatment Type',
|
'treatment_type' => 'Treatment Type',
|
||||||
'treatment_name' => 'Treatment Name',
|
'treatment_name' => 'Treatment Name',
|
||||||
'descriptions' => 'Descriptions',
|
'descriptions' => 'Descriptions',
|
||||||
'treatment_cost' => 'Cost (Rial)',
|
'treatment_cost' => 'Cost (Toman)',
|
||||||
'created_at' => 'Created At',
|
'created_at' => 'Created At',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,9 @@
|
||||||
'last_sync' => 'آخرین همگامسازی',
|
'last_sync' => 'آخرین همگامسازی',
|
||||||
'pending_changes' => 'تغییرات ارسال نشده',
|
'pending_changes' => 'تغییرات ارسال نشده',
|
||||||
'peer_address' => 'آدرس سیستم مقابل',
|
'peer_address' => 'آدرس سیستم مقابل',
|
||||||
|
'peer_connected' => 'آنلاین',
|
||||||
|
'peer_disconnected' => 'آفلاین',
|
||||||
|
'peer_checking' => 'در حال بررسی...',
|
||||||
'never' => 'هرگز',
|
'never' => 'هرگز',
|
||||||
'setup_guide' => 'راهنمای تنظیم',
|
'setup_guide' => 'راهنمای تنظیم',
|
||||||
'setup_description' => 'برای فعالسازی همگامسازی، مقادیر زیر را در فایل .env هر دو سیستم تنظیم کنید:',
|
'setup_description' => 'برای فعالسازی همگامسازی، مقادیر زیر را در فایل .env هر دو سیستم تنظیم کنید:',
|
||||||
|
|
@ -115,9 +118,13 @@
|
||||||
'description' => 'تمامی عملیات انجام شده توسط کاربران در لیست عملیات کاربران ثبت می گردد. همچنین عملیات همگام سازی دستگاههای آقلاین نیز در این لیست می آید.',
|
'description' => 'تمامی عملیات انجام شده توسط کاربران در لیست عملیات کاربران ثبت می گردد. همچنین عملیات همگام سازی دستگاههای آقلاین نیز در این لیست می آید.',
|
||||||
],
|
],
|
||||||
'backup' => [
|
'backup' => [
|
||||||
'label' => 'پشتیبانگیری',
|
'label' => 'پشتیبانگیری',
|
||||||
'success' => 'پشتیبانگیری با موفقیت انجام شد',
|
'success' => 'پشتیبانگیری با موفقیت انجام شد',
|
||||||
'failed' => 'خطا در پشتیبانگیری: فایل دیتابیس یافت نشد',
|
'failed' => 'خطا در پشتیبانگیری: فایل دیتابیس یافت نشد',
|
||||||
|
'status_preparing' => 'در حال آمادهسازی...',
|
||||||
|
'status_compressing' => 'در حال فشردهسازی فایلها...',
|
||||||
|
'status_ready' => 'فایل پشتیبان آماده دانلود است ✓',
|
||||||
|
'download_button' => 'دانلود فایل پشتیبان',
|
||||||
],
|
],
|
||||||
'import' => [
|
'import' => [
|
||||||
'label' => 'ورود از دیتابیس',
|
'label' => 'ورود از دیتابیس',
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
'illness' => '۲. کدامیک از بیماریهای زیر را داشته و یا دارید؟',
|
'illness' => '۲. کدامیک از بیماریهای زیر را داشته و یا دارید؟',
|
||||||
'surgery_notes' => '۳. توضیحات عمل',
|
'surgery_notes' => '۳. توضیحات عمل',
|
||||||
'media' => '۴. تصاویر بیمار',
|
'media' => '۴. تصاویر بیمار',
|
||||||
|
'record_info' => 'اطلاعات ثبت',
|
||||||
],
|
],
|
||||||
'fields' => [
|
'fields' => [
|
||||||
'icno' => 'کد ملی',
|
'icno' => 'کد ملی',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
'treatment_type' => 'نوع درمان',
|
'treatment_type' => 'نوع درمان',
|
||||||
'treatment_name' => 'نام درمان',
|
'treatment_name' => 'نام درمان',
|
||||||
'descriptions' => 'توضیحات',
|
'descriptions' => 'توضیحات',
|
||||||
'treatment_cost' => 'هزینه (ریال)',
|
'treatment_cost' => 'هزینه (تومان)',
|
||||||
'created_at' => 'تاریخ ثبت',
|
'created_at' => 'تاریخ ثبت',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2",
|
||||||
|
"@tiptap/core": "^3.22.4",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
|
|
@ -1357,6 +1358,46 @@
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tiptap/core": {
|
||||||
|
"version": "3.22.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.4.tgz",
|
||||||
|
"integrity": "sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/pm": "3.22.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tiptap/pm": {
|
||||||
|
"version": "3.22.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.4.tgz",
|
||||||
|
"integrity": "sha512-hj8Qka6WcHRllHUdeSjDnq2XaisUo4KsoGJc1WcFpoa1Yd+OeD861zUMnV7DFVGdZRy45Obht0CUYJpXQ4yA4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-changeset": "^2.3.0",
|
||||||
|
"prosemirror-commands": "^1.6.2",
|
||||||
|
"prosemirror-dropcursor": "^1.8.1",
|
||||||
|
"prosemirror-gapcursor": "^1.3.2",
|
||||||
|
"prosemirror-history": "^1.4.1",
|
||||||
|
"prosemirror-keymap": "^1.2.2",
|
||||||
|
"prosemirror-model": "^1.24.1",
|
||||||
|
"prosemirror-schema-list": "^1.5.0",
|
||||||
|
"prosemirror-state": "^1.4.3",
|
||||||
|
"prosemirror-tables": "^1.6.4",
|
||||||
|
"prosemirror-transform": "^1.10.2",
|
||||||
|
"prosemirror-view": "^1.38.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
|
|
@ -2248,6 +2289,13 @@
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/orderedmap": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
|
@ -2298,6 +2346,147 @@
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prosemirror-changeset": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-transform": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-commands": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-model": "^1.0.0",
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"prosemirror-transform": "^1.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-dropcursor": {
|
||||||
|
"version": "1.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
|
||||||
|
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"prosemirror-transform": "^1.1.0",
|
||||||
|
"prosemirror-view": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-gapcursor": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-keymap": "^1.0.0",
|
||||||
|
"prosemirror-model": "^1.0.0",
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"prosemirror-view": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-history": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-state": "^1.2.2",
|
||||||
|
"prosemirror-transform": "^1.0.0",
|
||||||
|
"prosemirror-view": "^1.31.0",
|
||||||
|
"rope-sequence": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-keymap": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"w3c-keyname": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-model": {
|
||||||
|
"version": "1.25.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
|
||||||
|
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"orderedmap": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-schema-list": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-model": "^1.0.0",
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"prosemirror-transform": "^1.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-state": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-model": "^1.0.0",
|
||||||
|
"prosemirror-transform": "^1.0.0",
|
||||||
|
"prosemirror-view": "^1.27.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-tables": {
|
||||||
|
"version": "1.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
|
||||||
|
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-keymap": "^1.2.3",
|
||||||
|
"prosemirror-model": "^1.25.4",
|
||||||
|
"prosemirror-state": "^1.4.4",
|
||||||
|
"prosemirror-transform": "^1.10.5",
|
||||||
|
"prosemirror-view": "^1.41.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-transform": {
|
||||||
|
"version": "1.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
|
||||||
|
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-model": "^1.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prosemirror-view": {
|
||||||
|
"version": "1.41.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
|
||||||
|
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"prosemirror-model": "^1.20.0",
|
||||||
|
"prosemirror-state": "^1.0.0",
|
||||||
|
"prosemirror-transform": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
|
@ -2360,6 +2549,13 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rope-sequence": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rxjs": {
|
"node_modules/rxjs": {
|
||||||
"version": "7.8.2",
|
"version": "7.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||||
|
|
@ -2592,6 +2788,13 @@
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2",
|
||||||
|
"@tiptap/core": "^3.22.4",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0.0",
|
"laravel-vite-plugin": "^2.0.0",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
<IfModule mod_negotiation.c>
|
||||||
|
Options -MultiViews -Indexes
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Handle Authorization Header
|
||||||
|
RewriteCond %{HTTP:Authorization} .
|
||||||
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
|
||||||
|
# Handle X-XSRF-Token Header
|
||||||
|
RewriteCond %{HTTP:x-xsrf-token} .
|
||||||
|
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||||
|
|
||||||
|
# Redirect Trailing Slashes If Not A Folder...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_URI} (.+)/$
|
||||||
|
RewriteRule ^ %1 [L,R=301]
|
||||||
|
|
||||||
|
# Send Requests To Front Controller...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</IfModule>
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
window.toggleRichEditorFullscreen=e=>{e.classList.toggle("fullscreen"),e.querySelector(".fullscreen-toggle").classList.toggle("fi-active");let s=function(c){c.key==="Escape"&&(e.classList.remove("fullscreen"),e.querySelector(".fullscreen-toggle").classList.remove("fi-active"),document.removeEventListener("keydown",s))};e.classList.contains("fullscreen")?(document.addEventListener("keydown",s),e.setAttribute("tabindex","-1"),e.focus()):e.removeAttribute("tabindex")};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-400-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-arabic-400-normal-C4W5XURk.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-400-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-400-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-arabic-400-normal-DMZFCm7K.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-400-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-500-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-arabic-500-normal-Dqq3-xo3.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-500-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-500-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-arabic-500-normal-C_lbnnKa.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-500-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-600-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-arabic-600-normal-CWYTfCgi.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-600-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-600-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-arabic-600-normal-CPKvAnd1.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-600-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-700-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-arabic-700-normal-B5nPuCFv.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-700-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-700-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-arabic-700-normal-Dge_DOjm.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-arabic-700-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-400-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-400-normal-GKyMcI03.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-400-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-400-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-400-normal-BT_DHTc7.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-400-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-500-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-500-normal-Bg_BALlD.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-500-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-500-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-500-normal-6zZzgpg4.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-500-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-600-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-600-normal-BxJiDPKT.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-600-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-600-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-600-normal-D-zF-Oec.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-600-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-700-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-700-normal-DrB0PBU6.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-700-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-700-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-700-normal-9BlbvDRV.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-700-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-400-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-400-normal-DPxMaNjI.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-400-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-400-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-400-normal-BdGhO0lm.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-400-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-500-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-500-normal-4XgegWYb.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-500-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-500-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-500-normal-CgxvvVrG.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-500-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-600-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-600-normal-Di8rk35l.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-600-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-600-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-600-normal-CyxCUfFz.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-600-normal.woff2"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-700-normal.woff": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-700-normal-DMYLqBto.woff",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-700-normal.woff"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-700-normal.woff2": {
|
||||||
|
"file": "assets/vazirmatn-latin-ext-700-normal-BfoXmNMx.woff2",
|
||||||
|
"src": "node_modules/@fontsource/vazirmatn/files/vazirmatn-latin-ext-700-normal.woff2"
|
||||||
|
},
|
||||||
|
"resources/css/app.css": {
|
||||||
|
"file": "assets/app-_TFpbERp.css",
|
||||||
|
"src": "resources/css/app.css",
|
||||||
|
"isEntry": true,
|
||||||
|
"name": "app",
|
||||||
|
"names": [
|
||||||
|
"app.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resources/css/filament/admin/theme.css": {
|
||||||
|
"file": "assets/theme-CFJJ2M3j.css",
|
||||||
|
"src": "resources/css/filament/admin/theme.css",
|
||||||
|
"isEntry": true,
|
||||||
|
"name": "theme",
|
||||||
|
"names": [
|
||||||
|
"theme.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resources/css/vazirmatn.css": {
|
||||||
|
"file": "assets/vazirmatn-CE-JssqN.css",
|
||||||
|
"src": "resources/css/vazirmatn.css",
|
||||||
|
"isEntry": true,
|
||||||
|
"name": "vazirmatn",
|
||||||
|
"names": [
|
||||||
|
"vazirmatn.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resources/js/app.js": {
|
||||||
|
"file": "assets/app-BuG9aa18.js",
|
||||||
|
"name": "app",
|
||||||
|
"src": "resources/js/app.js",
|
||||||
|
"isEntry": true
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||||
|
<rect width="64" height="64" rx="14" fill="#2563eb"/>
|
||||||
|
<!-- Medical cross -->
|
||||||
|
<rect x="26" y="14" width="12" height="36" rx="3" fill="white"/>
|
||||||
|
<rect x="14" y="26" width="36" height="12" rx="3" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 306 B |
|
|
@ -0,0 +1 @@
|
||||||
|
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
define('LARAVEL_START', microtime(true));
|
||||||
|
|
||||||
|
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||||
|
require $maintenance;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__.'/../vendor/autoload.php';
|
||||||
|
|
||||||
|
/** @var Application $app */
|
||||||
|
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||||
|
|
||||||
|
$app->handleRequest(Request::capture());
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
|
@ -210,10 +210,36 @@ [dir="rtl"] .prose p {
|
||||||
}
|
}
|
||||||
|
|
||||||
.fi-photo-gallery .filepond--root {
|
.fi-photo-gallery .filepond--root {
|
||||||
|
contain: layout style !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
min-height: 60px !important;
|
min-height: 60px !important;
|
||||||
|
height: auto !important;
|
||||||
margin-top: 8px !important;
|
margin-top: 8px !important;
|
||||||
overflow: visible !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,
|
.fi-photo-gallery-view .filepond--root,
|
||||||
[data-hide-dropzone] .filepond--root {
|
[data-hide-dropzone] .filepond--root {
|
||||||
|
|
@ -240,6 +266,12 @@ .fi-photo-gallery .filepond--item {
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
flex-shrink: 0 !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 {
|
.fi-photo-gallery .filepond--image-preview-wrapper {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
|
|
@ -249,16 +281,18 @@ .fi-photo-gallery .filepond--image-preview-wrapper {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
|
transform: none !important;
|
||||||
}
|
}
|
||||||
.fi-photo-gallery .filepond--image-preview-overlay,
|
.fi-photo-gallery .filepond--image-preview-overlay,
|
||||||
.fi-photo-gallery .filepond--image-preview {
|
.fi-photo-gallery .filepond--image-preview {
|
||||||
height: 56px !important;
|
height: 100% !important;
|
||||||
max-height: 56px !important;
|
max-height: none !important;
|
||||||
}
|
}
|
||||||
.fi-photo-gallery .filepond--image-preview canvas {
|
.fi-photo-gallery .filepond--image-preview canvas {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
object-fit: cover !important;
|
object-fit: cover !important;
|
||||||
|
display: block !important;
|
||||||
}
|
}
|
||||||
.fi-photo-gallery .filepond--panel-root,
|
.fi-photo-gallery .filepond--panel-root,
|
||||||
.fi-photo-gallery .filepond--panel-top,
|
.fi-photo-gallery .filepond--panel-top,
|
||||||
|
|
@ -279,15 +313,38 @@ .fi-photo-gallery .filepond--file-action-button {
|
||||||
.fi-photo-gallery .filepond--file-info {
|
.fi-photo-gallery .filepond--file-info {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
.fi-photo-gallery .filepond--drop-label {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fi-video-gallery .filepond--root {
|
.fi-video-gallery .filepond--root {
|
||||||
|
contain: layout style !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
min-height: 60px !important;
|
min-height: 60px !important;
|
||||||
|
height: auto !important;
|
||||||
margin-top: 8px !important;
|
margin-top: 8px !important;
|
||||||
overflow: visible !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 {
|
.fi-video-gallery .filepond--list {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
|
|
@ -337,9 +394,6 @@ .fi-video-gallery .filepond--file-action-button {
|
||||||
width: 1.4rem !important;
|
width: 1.4rem !important;
|
||||||
height: 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-remove-item,
|
||||||
.fi-photo-gallery-view .filepond--file-action-button.filepond--action-revert-item-processing,
|
.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 {
|
#fi-lb-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1rem;
|
top: 1rem;
|
||||||
right: 1rem;
|
left: 1rem;
|
||||||
|
right: auto;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
|
|
@ -465,3 +520,139 @@ #fi-lb-hint {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
direction: rtl;
|
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
|
<button
|
||||||
x-data
|
x-data
|
||||||
|
|
@ -26,9 +40,17 @@ class="relative"
|
||||||
failed: false,
|
failed: false,
|
||||||
interval: null,
|
interval: null,
|
||||||
|
|
||||||
|
showBackupProgress: false,
|
||||||
|
backupProgress: 0,
|
||||||
|
backupStatusText: '',
|
||||||
|
backupDone: false,
|
||||||
|
backupInterval: null,
|
||||||
|
backupUrl: null,
|
||||||
|
|
||||||
confirmSync() {
|
confirmSync() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.showConfirm = true;
|
this.showConfirm = true;
|
||||||
|
$wire.checkPeerStatus();
|
||||||
},
|
},
|
||||||
|
|
||||||
startSync() {
|
startSync() {
|
||||||
|
|
@ -60,6 +82,33 @@ class="relative"
|
||||||
if (this.done) {
|
if (this.done) {
|
||||||
window.location.reload();
|
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="
|
x-on:sync-finished.window="
|
||||||
|
|
@ -73,6 +122,13 @@ class="relative"
|
||||||
failed = true;
|
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"
|
@click.outside="open = false"
|
||||||
>
|
>
|
||||||
<button
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="open = false"
|
@click="startBackup()"
|
||||||
wire:click="runBackup"
|
|
||||||
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"
|
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" />
|
<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>
|
</div>
|
||||||
</template>
|
</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>
|
</div>
|
||||||
@endcan
|
@endcan
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
.btn-active { background: #16a34a; color: #fff; cursor: default; }
|
.btn-active { background: #16a34a; color: #fff; cursor: default; }
|
||||||
.btn-admission{ background: #d97706; color: #fff; }
|
.btn-admission{ background: #d97706; color: #fff; }
|
||||||
.btn-lab { background: #6b7280; color: #fff; }
|
.btn-lab { background: #6b7280; color: #fff; }
|
||||||
|
.btn-both { background: #7c3aed; color: #fff; }
|
||||||
.btn-back { background: #374151; color: #d1d5db; }
|
.btn-back { background: #374151; color: #d1d5db; }
|
||||||
|
|
||||||
.toolbar-divider { flex: 1; }
|
.toolbar-divider { flex: 1; }
|
||||||
|
|
@ -232,6 +233,10 @@
|
||||||
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
||||||
پرینت آزمایش
|
پرینت آزمایش
|
||||||
</a>
|
</a>
|
||||||
|
<a class="toolbar-btn btn-both"
|
||||||
|
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'both']) }}">
|
||||||
|
نسخه + آزمایش
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="toolbar-divider"></div>
|
<div class="toolbar-divider"></div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -268,6 +268,11 @@
|
||||||
|
|
||||||
.print-section {
|
.print-section {
|
||||||
width: 210mm !important;
|
width: 210mm !important;
|
||||||
|
height: 148mm !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prescription-container {
|
.prescription-container {
|
||||||
|
|
@ -306,16 +311,16 @@
|
||||||
<a class="toolbar-btn btn-print"
|
<a class="toolbar-btn btn-print"
|
||||||
href="{{ route('prescription.print', $prescription) }}">
|
href="{{ route('prescription.print', $prescription) }}">
|
||||||
نسخه تنها
|
نسخه تنها
|
||||||
|
</a>
|
||||||
|
<a class="toolbar-btn btn-admission"
|
||||||
|
href="{{ route('prescription.print.admission', $prescription) }}">
|
||||||
|
پرینت نسخه پذیرش
|
||||||
</a>
|
</a>
|
||||||
<a class="toolbar-btn btn-lab"
|
<a class="toolbar-btn btn-lab"
|
||||||
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
href="{{ route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']) }}">
|
||||||
آزمایش تنها
|
آزمایش تنها
|
||||||
</a>
|
</a>
|
||||||
<span class="toolbar-btn btn-active">✓ نسخه + آزمایش</span>
|
<span class="toolbar-btn btn-active">✓ نسخه + آزمایش</span>
|
||||||
<a class="toolbar-btn btn-admission"
|
|
||||||
href="{{ route('prescription.print.admission', $prescription) }}">
|
|
||||||
پرینت نسخه پذیرش
|
|
||||||
</a>
|
|
||||||
@elseif ($type === 'prescription')
|
@elseif ($type === 'prescription')
|
||||||
<span class="toolbar-btn btn-active">✓ پرینت نسخه</span>
|
<span class="toolbar-btn btn-active">✓ پرینت نسخه</span>
|
||||||
<a class="toolbar-btn btn-admission"
|
<a class="toolbar-btn btn-admission"
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue