feat: initial project setup with Laravel 12 and Filament v4
This commit is contained in:
parent
eca5d74b36
commit
09ee1cb743
|
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[compose.yaml]
|
||||
indent_size = 4
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:Cwol1ZZBqRAY2Em+k6lDh8PvD2RkmfSCSR5c3t+ypjg=
|
||||
APP_DEBUG=true
|
||||
|
||||
APP_LOCALE=fa
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
APP_URL=http://localhost:8000
|
||||
|
||||
SYNC_PEER_IP=192.168.1.8
|
||||
SYNC_PEER_PORT=8000
|
||||
SYNC_TOKEN=matab-secret-2026
|
||||
|
||||
SMS_API_URL=http://94.101.184.91/api/sms
|
||||
SMS_API_KEY=surgery-sms-2024
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
|
|
@ -1,25 +1,26 @@
|
|||
# ---> Laravel
|
||||
/vendor/
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Laravel 4 specific
|
||||
bootstrap/compiled.php
|
||||
app/storage/
|
||||
|
||||
# Laravel 5 & Lumen specific
|
||||
public/storage
|
||||
public/hot
|
||||
|
||||
# Laravel 5 & Lumen specific with changed public path
|
||||
public_html/storage
|
||||
public_html/hot
|
||||
|
||||
storage/*.key
|
||||
*.log
|
||||
.DS_Store
|
||||
/scripts/php/
|
||||
/.claude
|
||||
.env
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class JalaliDatetime implements CastsAttributes
|
||||
{
|
||||
public function __construct(
|
||||
private string $format = 'Y/m/d H:i'
|
||||
) {}
|
||||
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ?string
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Jalalian::fromCarbon(Carbon::parse($value))->format($this->format);
|
||||
} catch (\Throwable $e) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = Role::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('id')
|
||||
->label('ID'),
|
||||
ExportColumn::make('name')
|
||||
->label(__('filament-shield::filament-shield.column.name')),
|
||||
ExportColumn::make('guard_name')
|
||||
->label(__('filament-shield::filament-shield.column.guard_name')),
|
||||
ExportColumn::make('permissions_count')
|
||||
->label(__('filament-shield::filament-shield.column.permissions'))
|
||||
->counts('permissions'),
|
||||
ExportColumn::make('updated_at')
|
||||
->label(__('filament-shield::filament-shield.column.updated_at')),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your role export has completed and ' . Number::format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
|
||||
|
||||
if ($failedRowsCount = $export->getFailedRowsCount()) {
|
||||
$body .= ' ' . Number::format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Exports;
|
||||
|
||||
use App\Models\User;
|
||||
use Filament\Actions\Exports\ExportColumn;
|
||||
use Filament\Actions\Exports\Exporter;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class UserExporter extends Exporter
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
public static function getColumns(): array
|
||||
{
|
||||
return [
|
||||
ExportColumn::make('id')
|
||||
->label('ID'),
|
||||
ExportColumn::make('name')
|
||||
->label(__('users.fields.name')),
|
||||
ExportColumn::make('username')
|
||||
->label(__('users.fields.username')),
|
||||
ExportColumn::make('roles.name')
|
||||
->label(__('users.fields.roles'))
|
||||
->listAsJson(),
|
||||
ExportColumn::make('created_at')
|
||||
->label(__('users.fields.created_at')),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getCompletedNotificationBody(Export $export): string
|
||||
{
|
||||
$body = 'Your user export has completed and ' . Number::format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
|
||||
|
||||
if ($failedRowsCount = $export->getFailedRowsCount()) {
|
||||
$body .= ' ' . Number::format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Auth;
|
||||
|
||||
use Filament\Auth\Pages\Login as BaseLogin;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use SensitiveParameter;
|
||||
|
||||
class Login extends BaseLogin
|
||||
{
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('username')
|
||||
->label('نام کاربری')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->autocomplete('username')
|
||||
->autofocus()
|
||||
->validationMessages([
|
||||
'required' => 'وارد کردن نام کاربری الزامی است.',
|
||||
'max' => 'نام کاربری نباید بیشتر از ۲۵۵ کاراکتر باشد.',
|
||||
]),
|
||||
$this->getPasswordFormComponent()
|
||||
->validationMessages([
|
||||
'required' => 'وارد کردن رمز عبور الزامی است.',
|
||||
]),
|
||||
$this->getRememberFormComponent(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getCredentialsFromFormData(#[SensitiveParameter] array $data): array
|
||||
{
|
||||
return [
|
||||
'username' => $data['username'],
|
||||
'password' => $data['password'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function throwFailureValidationException(): never
|
||||
{
|
||||
throw ValidationException::withMessages([
|
||||
'data.username' => __('filament-panels::auth/pages/login.messages.failed'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use Filament\Pages\Dashboard as BaseDashboard;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class Dashboard extends BaseDashboard
|
||||
{
|
||||
protected string $view = 'filament.pages.dashboard';
|
||||
|
||||
public function getTitle(): string|Htmlable
|
||||
{
|
||||
return __('navigation.dashboard.title');
|
||||
}
|
||||
|
||||
public function getHeading(): string|Htmlable
|
||||
{
|
||||
return new HtmlString('');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Filament\Actions\Action as FilamentAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\FontFamily;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Grouping\Group;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class SmsLogsPage extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
protected string $view = 'filament.pages.sms-logs';
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('View:SmsLogsPage');
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-chat-bubble-left-ellipsis';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.settings');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.sms_logs.label');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('navigation.sms_logs.title');
|
||||
}
|
||||
|
||||
public array $stats = ['patients' => 0, 'total' => 0, 'sent' => 0, 'pending' => 0, 'failed' => 0];
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->records(fn (array $filters, ?string $search, int $page, int $recordsPerPage) =>
|
||||
$this->fetchRecords($filters, $search, $page, $recordsPerPage)
|
||||
)
|
||||
->columns([
|
||||
|
||||
Tables\Columns\TextColumn::make('message_type')
|
||||
->label('نوع پیام')
|
||||
->getStateUsing(fn (array $record): string => $record['message_type'] ?? '')
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'initial' => 'پیام اولیه',
|
||||
'reminder_7days' => 'یادآوری ۷ روز',
|
||||
'reminder_3days' => 'یادآوری ۳ روز',
|
||||
'reminder_after_registration' => 'یادآوری پس از ثبت',
|
||||
'day_of_surgery' => 'روز عمل',
|
||||
default => $state,
|
||||
})
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'initial' => 'info',
|
||||
'day_of_surgery' => 'success',
|
||||
'reminder_3days' => 'pink',
|
||||
default => 'warning',
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->label('وضعیت')
|
||||
->getStateUsing(fn (array $record): string => $record['status'] ?? '')
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'sent' => 'ارسال شده',
|
||||
'pending' => 'در انتظار',
|
||||
'failed' => 'ناموفق',
|
||||
default => $state,
|
||||
})
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'sent' => 'success',
|
||||
'pending' => 'warning',
|
||||
'failed' => 'danger',
|
||||
default => 'gray',
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('scheduled_at')
|
||||
->label('زمانبندی شده')
|
||||
->getStateUsing(fn (array $record): string => $this->toJalali($record['scheduled_at'] ?? null)),
|
||||
|
||||
Tables\Columns\TextColumn::make('sent_at')
|
||||
->label('زمان ارسال')
|
||||
->getStateUsing(fn (array $record): string => $this->toJalali($record['sent_at'] ?? null)),
|
||||
|
||||
])
|
||||
->groups([
|
||||
Group::make('patient_key')
|
||||
->titlePrefixedWithLabel(false)
|
||||
->getTitleFromRecordUsing(fn (array $record): string => $record['patient_name'] ?? '-')
|
||||
->getDescriptionFromRecordUsing(fn (array $record): string =>
|
||||
($record['phone_number'] ?? '') .
|
||||
($record['surgery_date']
|
||||
? ' | تاریخ عمل: ' . $this->toJalali($record['surgery_date'], 'Y/m/d')
|
||||
: '')
|
||||
)
|
||||
->collapsible(),
|
||||
])
|
||||
->defaultGroup('patient_key')
|
||||
->collapsedGroupsByDefault()
|
||||
->groupingSettingsHidden()
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')
|
||||
->label('وضعیت')
|
||||
->options([
|
||||
'sent' => 'ارسال شده',
|
||||
'pending' => 'در انتظار',
|
||||
'failed' => 'ناموفق',
|
||||
]),
|
||||
|
||||
Tables\Filters\Filter::make('patient_name')
|
||||
->label('نام بیمار')
|
||||
->form([
|
||||
TextInput::make('patient_name')
|
||||
->label('نام بیمار')
|
||||
->placeholder('جستجو...'),
|
||||
])
|
||||
->indicateUsing(fn (array $data): ?string => filled($data['patient_name'] ?? null)
|
||||
? 'نام: ' . $data['patient_name']
|
||||
: null),
|
||||
|
||||
Tables\Filters\Filter::make('phone_number')
|
||||
->label('شماره موبایل')
|
||||
->form([
|
||||
TextInput::make('phone_number')
|
||||
->label('شماره موبایل')
|
||||
->placeholder('09...'),
|
||||
])
|
||||
->indicateUsing(fn (array $data): ?string => filled($data['phone_number'] ?? null)
|
||||
? 'موبایل: ' . $data['phone_number']
|
||||
: null),
|
||||
])
|
||||
->recordActions([
|
||||
FilamentAction::make('delete_message')
|
||||
->label('حذف پیام')
|
||||
->icon('heroicon-o-trash')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('حذف پیام')
|
||||
->modalDescription('آیا از حذف این پیام مطمئن هستید؟')
|
||||
->modalSubmitActionLabel('بله، حذف شود')
|
||||
->action(fn (array $record) => $this->deleteSingleMessage((int) $record['id'])),
|
||||
|
||||
FilamentAction::make('delete_patient')
|
||||
->label('حذف همه پیامهای بیمار')
|
||||
->icon('heroicon-o-user-minus')
|
||||
->color('danger')
|
||||
->outlined()
|
||||
->requiresConfirmation()
|
||||
->modalHeading('حذف همه پیامهای بیمار')
|
||||
->modalDescription('آیا از حذف تمام پیامهای این بیمار مطمئن هستید؟')
|
||||
->modalSubmitActionLabel('بله، حذف شود')
|
||||
->action(fn (array $record) => $this->deletePatientMessages(
|
||||
$record['phone_number'],
|
||||
$record['surgery_date']
|
||||
)),
|
||||
])
|
||||
->paginationPageOptions([5, 10, 25, 50]);
|
||||
}
|
||||
|
||||
protected function fetchRecords(
|
||||
array $filters,
|
||||
?string $search,
|
||||
int $page,
|
||||
int $recordsPerPage,
|
||||
): LengthAwarePaginator {
|
||||
|
||||
$query = array_filter([
|
||||
'status' => $filters['status']['value'] ?? null,
|
||||
'patientName' => $filters['patient_name']['patient_name'] ?? $search,
|
||||
'phoneNumber' => $filters['phone_number']['phone_number'] ?? null,
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = Http::timeout(15)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->get(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/sms-logs-grouped', $query);
|
||||
|
||||
if ($response->failed() || ! ($response->json('success') ?? false)) {
|
||||
\Illuminate\Support\Facades\Log::warning('SmsLogsPage: API returned failure', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
'api_url' => \App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')),
|
||||
]);
|
||||
Notification::make()
|
||||
->title('خطا در دریافت لاگهای پیامک')
|
||||
->body('کد: ' . $response->status() . ' | ' . ($response->json('message') ?? $response->body()))
|
||||
->danger()
|
||||
->send();
|
||||
return new LengthAwarePaginator([], 0, $recordsPerPage, $page);
|
||||
}
|
||||
|
||||
$grouped = $response->json('data') ?? [];
|
||||
|
||||
$this->recalcStats($grouped);
|
||||
|
||||
$totalPatients = count($grouped);
|
||||
$patientsOnPage = array_slice($grouped, ($page - 1) * $recordsPerPage, $recordsPerPage);
|
||||
|
||||
$rows = [];
|
||||
foreach ($patientsOnPage as $patient) {
|
||||
$patientKey = ($patient['phone_number'] ?? '') . '|' . ($patient['surgery_date'] ?? '');
|
||||
|
||||
foreach ($patient['messages'] ?? [] as $msg) {
|
||||
$rows[] = [
|
||||
'id' => $msg['id'],
|
||||
'patient_key' => $patientKey,
|
||||
'patient_name' => $patient['patient_name'] ?? '-',
|
||||
'phone_number' => $patient['phone_number'] ?? '',
|
||||
'surgery_date' => $patient['surgery_date'] ?? '',
|
||||
'message_type' => $msg['message_type'] ?? '',
|
||||
'status' => $msg['status'] ?? '',
|
||||
'scheduled_at' => $msg['scheduled_at'] ?? null,
|
||||
'sent_at' => $msg['sent_at'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return new LengthAwarePaginator(
|
||||
items: $rows,
|
||||
total: $totalPatients,
|
||||
perPage: $recordsPerPage,
|
||||
currentPage: $page,
|
||||
);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
\Illuminate\Support\Facades\Log::error('SmsLogsPage: API exception', [
|
||||
'error' => $e->getMessage(),
|
||||
'api_url' => \App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')),
|
||||
]);
|
||||
Notification::make()
|
||||
->title('خطا در اتصال به سرویس پیامک')
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
return new LengthAwarePaginator([], 0, $recordsPerPage, $page);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteSingleMessage(int $id): void
|
||||
{
|
||||
try {
|
||||
$response = Http::timeout(10)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->delete(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/sms-logs/' . $id);
|
||||
|
||||
if ($response->json('success') ?? false) {
|
||||
Notification::make()->title('پیام حذف شد')->success()->send();
|
||||
$this->resetTable();
|
||||
} else {
|
||||
Notification::make()->title($response->json('message') ?? 'خطا در حذف پیام')->danger()->send();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title('خطا: ' . $e->getMessage())->danger()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function deletePatientMessages(string $phoneNumber, string $surgeryDate): void
|
||||
{
|
||||
try {
|
||||
$response = Http::timeout(10)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->post(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/sms-logs/delete-patient', [
|
||||
'phoneNumber' => $phoneNumber,
|
||||
'surgeryDate' => $surgeryDate,
|
||||
]);
|
||||
|
||||
if ($response->json('success') ?? false) {
|
||||
Notification::make()->title('همه پیامهای بیمار حذف شد')->success()->send();
|
||||
$this->resetTable();
|
||||
} else {
|
||||
Notification::make()->title($response->json('message') ?? 'خطا در حذف پیامها')->danger()->send();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title('خطا: ' . $e->getMessage())->danger()->send();
|
||||
}
|
||||
}
|
||||
|
||||
public function toJalali(?string $dateStr, string $format = 'Y/m/d H:i'): string
|
||||
{
|
||||
if (! $dateStr) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
return Jalalian::fromCarbon(
|
||||
Carbon::parse($dateStr, 'UTC')->setTimezone('Asia/Tehran')
|
||||
)->format($format);
|
||||
} catch (\Throwable) {
|
||||
return $dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
private function recalcStats(array $grouped): void
|
||||
{
|
||||
$total = $sent = $pending = $failed = 0;
|
||||
|
||||
foreach ($grouped as $p) {
|
||||
$total += $p['stats']['total'] ?? 0;
|
||||
$sent += $p['stats']['sent'] ?? 0;
|
||||
$pending += $p['stats']['pending'] ?? 0;
|
||||
$failed += $p['stats']['failed'] ?? 0;
|
||||
}
|
||||
|
||||
$this->stats = [
|
||||
'patients' => count($grouped),
|
||||
'total' => $total,
|
||||
'sent' => $sent,
|
||||
'pending' => $pending,
|
||||
'failed' => $failed,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\SyncLog;
|
||||
use App\Services\SyncService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class SyncPage extends Page
|
||||
{
|
||||
protected string $view = 'filament.pages.sync-page';
|
||||
|
||||
protected static ?int $navigationSort = 99;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('View:SyncPage');
|
||||
}
|
||||
|
||||
public static function shouldRegisterNavigation(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-arrows-right-left';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.system');
|
||||
}
|
||||
|
||||
public string $report = '';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.sync.label');
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('navigation.sync.title');
|
||||
}
|
||||
|
||||
public function getPendingCount(): int
|
||||
{
|
||||
return SyncLog::changesSince(SyncLog::lastSyncId())->count();
|
||||
}
|
||||
|
||||
public function getLastSyncTime(): string
|
||||
{
|
||||
$last = SyncLog::where('action', 'synced')->latest('id')->first();
|
||||
if (! $last) {
|
||||
return __('navigation.sync.never');
|
||||
}
|
||||
|
||||
return Jalalian::fromCarbon($last->datetime->timezone('Asia/Tehran'))->format('Y/m/d H:i:s');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('sync')
|
||||
->label(__('navigation.sync.sync_with_peer'))
|
||||
->icon('heroicon-o-arrows-right-left')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('navigation.sync.confirm_heading'))
|
||||
->modalDescription(__('navigation.sync.confirm_description'))
|
||||
->modalSubmitActionLabel(__('navigation.sync.confirm_button'))
|
||||
->action('runSync'),
|
||||
|
||||
Action::make('backup')
|
||||
->label(__('navigation.backup.label'))
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
->color('success')
|
||||
->action('downloadBackup'),
|
||||
];
|
||||
}
|
||||
|
||||
public function runSync(): void
|
||||
{
|
||||
$service = SyncService::fromConfig();
|
||||
|
||||
if (! $service->isConfigured()) {
|
||||
Notification::make()
|
||||
->title(__('navigation.sync.error_title'))
|
||||
->body(__('navigation.sync.error_not_configured'))
|
||||
->danger()
|
||||
->send();
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $service->sync();
|
||||
|
||||
$this->report = implode("\n", $result['report']);
|
||||
|
||||
Notification::make()
|
||||
->title($result['success'] ? __('navigation.sync.success') : __('navigation.sync.failed'))
|
||||
->body(__('navigation.sync.see_report'))
|
||||
->color($result['success'] ? 'success' : 'warning')
|
||||
->send();
|
||||
}
|
||||
|
||||
public function downloadBackup(): \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
{
|
||||
$data = [
|
||||
'exported_at' => now()->toDateTimeString(),
|
||||
'last_sync_id' => SyncLog::lastSyncId(),
|
||||
'changes' => SyncLog::all()->toArray(),
|
||||
];
|
||||
|
||||
$filename = 'matab-backup-' . now()->format('Y-m-d_H-i-s') . '.json';
|
||||
|
||||
return response()->streamDownload(function () use ($data) {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
}, $filename, ['Content-Type' => 'application/json']);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\DoctorResource\Pages;
|
||||
use App\Models\Doctor;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class DoctorResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Doctor::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'first_name';
|
||||
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return ['first_name', 'last_name', 'speciality'];
|
||||
}
|
||||
|
||||
public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
|
||||
{
|
||||
return [
|
||||
__('navigation.doctors.last_name') => $record->last_name,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-user-circle';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.clinic');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.doctors.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.doctors.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.doctors.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('first_name')
|
||||
->label(__('doctors.fields.first_name'))
|
||||
->required()
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('last_name')
|
||||
->label(__('doctors.fields.last_name'))
|
||||
->required()
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('speciality')
|
||||
->label(__('doctors.fields.speciality'))
|
||||
->maxLength(100),
|
||||
|
||||
TextInput::make('license_id')
|
||||
->label(__('doctors.fields.license_id'))
|
||||
->maxLength(50),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('first_name')
|
||||
->label(__('doctors.fields.first_name'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('last_name')
|
||||
->label(__('doctors.fields.last_name'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('speciality')
|
||||
->label(__('doctors.fields.speciality'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('license_id')
|
||||
->label(__('doctors.fields.license_id'))
|
||||
->badge()
|
||||
->color('primary'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('doctors.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('last_name')
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('doctors.fields.first_name'),
|
||||
__('doctors.fields.last_name'),
|
||||
__('doctors.fields.speciality'),
|
||||
__('doctors.fields.license_id'),
|
||||
__('doctors.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->first_name,
|
||||
$record->last_name,
|
||||
$record->speciality,
|
||||
$record->license_id,
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'doctors-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Doctor::all();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('doctors.fields.first_name'),
|
||||
__('doctors.fields.last_name'),
|
||||
__('doctors.fields.speciality'),
|
||||
__('doctors.fields.license_id'),
|
||||
__('doctors.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->first_name,
|
||||
$record->last_name,
|
||||
$record->speciality,
|
||||
$record->license_id,
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'doctors-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Doctor::all();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.doctors-pdf',
|
||||
['records' => $records],
|
||||
'doctors-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDoctors::route('/'),
|
||||
'create' => Pages\CreateDoctor::route('/create'),
|
||||
'edit' => Pages\EditDoctor::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\DoctorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DoctorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateDoctor extends CreateRecord
|
||||
{
|
||||
protected static string $resource = DoctorResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\DoctorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DoctorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditDoctor extends EditRecord
|
||||
{
|
||||
protected static string $resource = DoctorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\DoctorResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DoctorResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListDoctors extends ListRecords
|
||||
{
|
||||
protected static string $resource = DoctorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\MedicationResource\Pages;
|
||||
use App\Models\Medication;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class MedicationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Medication::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-beaker';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.diseases');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.medications.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.medications.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.medications.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make(__('medications.sections.info'))
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('medications.fields.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('dosage_form')
|
||||
->label(__('medications.fields.dosage_form'))
|
||||
->maxLength(50)
|
||||
->placeholder('Tab, Cap, Amp, Drop, Serum, Cream ...'),
|
||||
|
||||
TextInput::make('strength')
|
||||
->label(__('medications.fields.strength'))
|
||||
->maxLength(100)
|
||||
->placeholder('500mg, 5mg/5ml ...'),
|
||||
|
||||
TextInput::make('quantity')
|
||||
->label(__('medications.fields.quantity'))
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
|
||||
TextInput::make('timing')
|
||||
->label(__('medications.fields.timing'))
|
||||
->maxLength(100)
|
||||
->placeholder('q8h, q12h, q24h ...'),
|
||||
|
||||
Textarea::make('notes')
|
||||
->label(__('medications.fields.notes'))
|
||||
->rows(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Toggle::make('is_default')
|
||||
->label(__('medications.fields.is_default')),
|
||||
|
||||
Toggle::make('is_active')
|
||||
->label(__('medications.fields.is_active'))
|
||||
->default(true),
|
||||
])
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('medications.fields.name'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('dosage_form')
|
||||
->label(__('medications.fields.dosage_form'))
|
||||
->badge()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('strength')
|
||||
->label(__('medications.fields.strength'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('quantity')
|
||||
->label(__('medications.fields.quantity'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('timing')
|
||||
->label(__('medications.fields.timing'))
|
||||
->placeholder('-'),
|
||||
|
||||
IconColumn::make('is_default')
|
||||
->label(__('medications.fields.is_default'))
|
||||
->boolean(),
|
||||
|
||||
IconColumn::make('is_active')
|
||||
->label(__('medications.fields.is_active'))
|
||||
->boolean(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('medications.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('name')
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
\Filament\Actions\BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListMedications::route('/'),
|
||||
'create' => Pages\CreateMedication::route('/create'),
|
||||
'edit' => Pages\EditMedication::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\MedicationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\MedicationResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class CreateMedication extends CreateRecord
|
||||
{
|
||||
protected static string $resource = MedicationResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\MedicationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\MedicationResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class EditMedication extends EditRecord
|
||||
{
|
||||
protected static string $resource = MedicationResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\MedicationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\MedicationResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class ListMedications extends ListRecords
|
||||
{
|
||||
protected static string $resource = MedicationResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::Full;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\OsurgAuditResource\Pages;
|
||||
use App\Models\SyncLog;
|
||||
use Filament\Resources\Resource;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OsurgAuditResource extends Resource
|
||||
{
|
||||
protected static ?string $model = SyncLog::class;
|
||||
|
||||
protected static bool $isGloballySearchable = false;
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-clipboard-document-list';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.settings');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_audit.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_audit.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_audit.title');
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label(__('sync_log.fields.id'))
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('datetime')
|
||||
->label(__('sync_log.fields.datetime'))
|
||||
->sortable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
}),
|
||||
|
||||
TextColumn::make('ip')
|
||||
->label(__('sync_log.fields.ip'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('user')
|
||||
->label(__('sync_log.fields.user'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('table_name')
|
||||
->label(__('sync_log.fields.table_name'))
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('action')
|
||||
->label(__('sync_log.fields.action'))
|
||||
->badge()
|
||||
->searchable()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'created' => 'success',
|
||||
'updated' => 'warning',
|
||||
'deleted' => 'danger',
|
||||
'synced' => 'gray',
|
||||
default => 'primary',
|
||||
}),
|
||||
|
||||
TextColumn::make('record_id')
|
||||
->label(__('sync_log.fields.record_id')),
|
||||
])
|
||||
->defaultSort('id', 'desc');
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOsurgAudits::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OsurgAuditResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OsurgAuditResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOsurgAudits extends ListRecords
|
||||
{
|
||||
protected static string $resource = OsurgAuditResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\OsurgInitialResource\Pages;
|
||||
use App\Models\OsurgInitial;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class OsurgInitialResource extends Resource
|
||||
{
|
||||
protected static ?string $model = OsurgInitial::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'init_parameter';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-adjustments-horizontal';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.settings');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_initials.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_initials.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.osurg_initials.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('init_parameter')
|
||||
->label(__('osurg_initials.fields.init_parameter'))
|
||||
->required()
|
||||
->maxLength(50)
|
||||
->unique(table: OsurgInitial::class, column: 'init_parameter', ignoreRecord: true),
|
||||
|
||||
TextInput::make('init_value')
|
||||
->label(__('osurg_initials.fields.init_value'))
|
||||
->maxLength(50),
|
||||
|
||||
FileUpload::make('attachment')
|
||||
->label(__('osurg_initials.fields.attachment'))
|
||||
->disk('public')
|
||||
->directory('initials')
|
||||
->image()
|
||||
->imagePreviewHeight('120')
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('init_parameter')
|
||||
->label(__('osurg_initials.fields.init_parameter'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('init_value')
|
||||
->label(__('osurg_initials.fields.init_value'))
|
||||
->searchable()
|
||||
->placeholder('—'),
|
||||
|
||||
ImageColumn::make('attachment')
|
||||
->label(__('osurg_initials.fields.attachment'))
|
||||
->disk('public')
|
||||
->square()
|
||||
->size(48),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('osurg_initials.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('osurg_initials.fields.init_parameter'),
|
||||
__('osurg_initials.fields.init_value'),
|
||||
__('osurg_initials.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->init_parameter,
|
||||
$record->init_value,
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'osurg-initials-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = OsurgInitial::all();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('osurg_initials.fields.init_parameter'),
|
||||
__('osurg_initials.fields.init_value'),
|
||||
__('osurg_initials.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->init_parameter,
|
||||
$record->init_value,
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'osurg-initials-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = OsurgInitial::all();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.osurg-initials-pdf',
|
||||
['records' => $records],
|
||||
'osurg-initials-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOsurgInitials::route('/'),
|
||||
'create' => Pages\CreateOsurgInitial::route('/create'),
|
||||
'edit' => Pages\EditOsurgInitial::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OsurgInitialResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OsurgInitialResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOsurgInitial extends CreateRecord
|
||||
{
|
||||
protected static string $resource = OsurgInitialResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OsurgInitialResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OsurgInitialResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOsurgInitial extends EditRecord
|
||||
{
|
||||
protected static string $resource = OsurgInitialResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OsurgInitialResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OsurgInitialResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOsurgInitials extends ListRecords
|
||||
{
|
||||
protected static string $resource = OsurgInitialResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PatientIllnessResource\Pages;
|
||||
use App\Models\PatientIllness;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Grouping\Group;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class PatientIllnessResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PatientIllness::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'illness';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-heart';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.diseases');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.patient_illness.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.patient_illness.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.patient_illness.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('illness')
|
||||
->label(__('patient_illness.fields.illness'))
|
||||
->required()
|
||||
->maxLength(200)
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('row_no')
|
||||
->label(__('patient_illness.fields.row_no'))
|
||||
->options([
|
||||
1 => __('patient_illness.row_options.first'),
|
||||
2 => __('patient_illness.row_options.second'),
|
||||
])
|
||||
->required(),
|
||||
|
||||
TextInput::make('priority')
|
||||
->label(__('patient_illness.fields.priority'))
|
||||
->numeric()
|
||||
->minValue(1),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('illness')
|
||||
->label(__('patient_illness.fields.illness'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('row_no')
|
||||
->label(__('patient_illness.fields.row_no'))
|
||||
->badge()
|
||||
->color('gray')
|
||||
->formatStateUsing(fn (int $state): string => $state === 1
|
||||
? __('patient_illness.row_options.first')
|
||||
: __('patient_illness.row_options.second'))
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('priority')
|
||||
->label(__('patient_illness.fields.priority'))
|
||||
->badge()
|
||||
->color('primary')
|
||||
->sortable(),
|
||||
])
|
||||
->reorderable('priority')
|
||||
->defaultSort('priority')
|
||||
->groups([
|
||||
Group::make('row_no')
|
||||
->label(__('patient_illness.fields.row_no'))
|
||||
->getTitleFromRecordUsing(fn (PatientIllness $record): string => $record->row_no === 1
|
||||
? __('patient_illness.row_options.first')
|
||||
: __('patient_illness.row_options.second')),
|
||||
])
|
||||
->defaultGroup('row_no')
|
||||
->groupingSettingsHidden()
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('patient_illness.fields.illness'),
|
||||
__('patient_illness.fields.row_no'),
|
||||
__('patient_illness.fields.priority'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->illness,
|
||||
$record->row_no,
|
||||
$record->priority,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'patient-illness-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = PatientIllness::orderBy('row_no')->orderBy('priority')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('patient_illness.fields.illness'),
|
||||
__('patient_illness.fields.row_no'),
|
||||
__('patient_illness.fields.priority'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->illness,
|
||||
$record->row_no,
|
||||
$record->priority,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'patient-illness-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = PatientIllness::orderBy('row_no')->orderBy('priority')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.patient-illness-pdf',
|
||||
['records' => $records],
|
||||
'patient-illness-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPatientIllnesses::route('/'),
|
||||
'create' => Pages\CreatePatientIllness::route('/create'),
|
||||
'edit' => Pages\EditPatientIllness::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientIllnessResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientIllnessResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePatientIllness extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PatientIllnessResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientIllnessResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientIllnessResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPatientIllness extends EditRecord
|
||||
{
|
||||
protected static string $resource = PatientIllnessResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientIllnessResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientIllnessResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPatientIllnesses extends ListRecords
|
||||
{
|
||||
protected static string $resource = PatientIllnessResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,998 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
use App\Filament\Resources\PatientResource\Pages;
|
||||
use App\Filament\Resources\PatientResource\RelationManagers\VisitsRelationManager;
|
||||
use App\Models\Medication;
|
||||
use App\Models\Patient;
|
||||
use App\Models\PatientIllness;
|
||||
use App\Models\Prescription;
|
||||
use App\Models\SurgeryAppointment;
|
||||
use App\Models\SurgeryCenter;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Radio;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TimePicker;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class PatientResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Patient::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'first_name';
|
||||
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return ['first_name', 'last_name', 'hand_phone'];
|
||||
}
|
||||
|
||||
public static function getGlobalSearchResultDetails(\Illuminate\Database\Eloquent\Model $record): array
|
||||
{
|
||||
return [
|
||||
__('navigation.patients.last_name') => $record->last_name,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-user-group';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.patients');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.patients.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.patients.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.patients.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
$yesNo = [
|
||||
'بله' => 'بله',
|
||||
'خیر' => 'خیر',
|
||||
];
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
|
||||
Section::make(__('patients.sections.patient_info'))
|
||||
->schema([
|
||||
TextInput::make('first_name')
|
||||
->label(__('patients.fields.first_name'))
|
||||
->required()
|
||||
->validationMessages([
|
||||
'required' => __('patients.validation.first_name_required'),
|
||||
])
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('last_name')
|
||||
->label(__('patients.fields.last_name'))
|
||||
->required()
|
||||
->validationMessages([
|
||||
'required' => __('patients.validation.last_name_required'),
|
||||
])
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('father_name')
|
||||
->label(__('patients.fields.father_name'))
|
||||
->required()
|
||||
->validationMessages([
|
||||
'required' => __('patients.validation.father_name_required'),
|
||||
])
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('icno')
|
||||
->label(__('patients.fields.icno'))
|
||||
->required()
|
||||
->maxLength(11),
|
||||
|
||||
TextInput::make('hand_phone')
|
||||
->label(__('patients.fields.hand_phone'))
|
||||
->required()
|
||||
->validationMessages([
|
||||
'required' => __('patients.validation.hand_phone_required'),
|
||||
])
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('age')
|
||||
->label(__('patients.fields.age'))
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
|
||||
DatePicker::make('birth_date')
|
||||
->label(__('patients.fields.birth_date'))
|
||||
->jalali(),
|
||||
|
||||
Radio::make('gender')
|
||||
->label(__('patients.fields.gender'))
|
||||
->options(['مرد' => 'مرد', 'زن' => 'زن'])
|
||||
->inline(),
|
||||
|
||||
Radio::make('marital_status')
|
||||
->label(__('patients.fields.marital_status'))
|
||||
->options(['مجرد' => 'مجرد', 'متاهل' => 'متاهل'])
|
||||
->inline(),
|
||||
|
||||
TextInput::make('insurance_no')
|
||||
->label(__('patients.fields.insurance_no'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('insurance')
|
||||
->label(__('patients.fields.insurance'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('education')
|
||||
->label(__('patients.fields.education'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('job')
|
||||
->label(__('patients.fields.job'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('home_phone')
|
||||
->label(__('patients.fields.home_phone'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('work_phone')
|
||||
->label(__('patients.fields.work_phone'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('other_phone')
|
||||
->label(__('patients.fields.other_phone'))
|
||||
->maxLength(50),
|
||||
|
||||
Textarea::make('home_address')
|
||||
->label(__('patients.fields.home_address'))
|
||||
->rows(2)
|
||||
->columnSpan(2),
|
||||
|
||||
Textarea::make('work_address')
|
||||
->label(__('patients.fields.work_address'))
|
||||
->rows(2)
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('refered_by')
|
||||
->label(__('patients.fields.refered_by'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('doc_id')
|
||||
->label(__('patients.fields.doc_id'))
|
||||
->maxLength(50),
|
||||
|
||||
Textarea::make('referal_reason')
|
||||
->label(__('patients.fields.referal_reason'))
|
||||
->rows(2)
|
||||
->columnSpan(2),
|
||||
])
|
||||
->columns(4)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('patients.sections.illness'))
|
||||
->schema([
|
||||
CheckboxList::make('current_illness_1')
|
||||
->label(__('patients.fields.current_illness_1'))
|
||||
->options(fn () => \Illuminate\Support\Facades\Cache::remember(
|
||||
'illness_options_1',
|
||||
now()->addDay(),
|
||||
fn () => PatientIllness::where('row_no', 1)->orderBy('priority')->pluck('illness', 'illness')->toArray()
|
||||
)),
|
||||
|
||||
CheckboxList::make('current_illness_2')
|
||||
->label(__('patients.fields.current_illness_2'))
|
||||
->options(fn () => \Illuminate\Support\Facades\Cache::remember(
|
||||
'illness_options_2',
|
||||
now()->addDay(),
|
||||
fn () => PatientIllness::where('row_no', 2)->orderBy('priority')->pluck('illness', 'illness')->toArray()
|
||||
)),
|
||||
|
||||
TextInput::make('blood_sugar')
|
||||
->label(__('patients.fields.blood_sugar'))
|
||||
->numeric(),
|
||||
|
||||
TextInput::make('blood_pressure')
|
||||
->label(__('patients.fields.blood_pressure'))
|
||||
->numeric(),
|
||||
|
||||
Radio::make('is_undercare')
|
||||
->label(__('patients.fields.is_undercare'))
|
||||
->options($yesNo)
|
||||
->inline(),
|
||||
|
||||
TextInput::make('undercare_reason')
|
||||
->label(__('patients.fields.undercare_reason'))
|
||||
->maxLength(50),
|
||||
|
||||
Radio::make('is_usingdrug')
|
||||
->label(__('patients.fields.is_usingdrug'))
|
||||
->options($yesNo)
|
||||
->inline(),
|
||||
|
||||
TextInput::make('underdrug_reason')
|
||||
->label(__('patients.fields.underdrug_reason'))
|
||||
->maxLength(50),
|
||||
|
||||
CheckboxList::make('has_alergyto')
|
||||
->label(__('patients.fields.has_alergyto'))
|
||||
->options([
|
||||
'پنی سیلین' => 'پنی سیلین',
|
||||
'داروی بی حسی' => 'داروی بی حسی',
|
||||
'مواد غذایی و دارویی دیگر' => 'مواد غذایی و دارویی دیگر',
|
||||
])
|
||||
->columns(3),
|
||||
|
||||
TextInput::make('alergy_reason')
|
||||
->label(__('patients.fields.alergy_reason'))
|
||||
->maxLength(50),
|
||||
|
||||
Textarea::make('description')
|
||||
->label(__('patients.fields.description'))
|
||||
->rows(2)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('patients.sections.surgery_notes'))
|
||||
->schema([
|
||||
RichEditor::make('surgery_before')
|
||||
->label(__('patients.fields.surgery_before'))
|
||||
->extraInputAttributes(['style' => 'min-height: 150px']),
|
||||
|
||||
RichEditor::make('sergery_after')
|
||||
->label(__('patients.fields.sergery_after'))
|
||||
->extraInputAttributes(['style' => 'min-height: 150px']),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('patients.sections.media'))
|
||||
->schema([
|
||||
FileUpload::make('photos_before')
|
||||
->label(__('patients.fields.photos_before'))
|
||||
->multiple()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('surgery_photos/before')
|
||||
->downloadable()
|
||||
->openable(),
|
||||
|
||||
FileUpload::make('photos_after')
|
||||
->label(__('patients.fields.photos_after'))
|
||||
->multiple()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('surgery_photos/after')
|
||||
->downloadable()
|
||||
->openable(),
|
||||
|
||||
FileUpload::make('videos')
|
||||
->label(__('patients.fields.videos'))
|
||||
->multiple()
|
||||
->disk('public')
|
||||
->directory('surgery_photos/videos')
|
||||
->acceptedFileTypes(['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska', 'video/webm', 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/aac'])
|
||||
->downloadable(),
|
||||
|
||||
FileUpload::make('audio_files')
|
||||
->label(__('patients.fields.audio_files'))
|
||||
->multiple()
|
||||
->disk('public')
|
||||
->directory('audio_files')
|
||||
->acceptedFileTypes(['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/aac'])
|
||||
->downloadable(),
|
||||
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->modifyQueryUsing(fn ($query) => $query->with(['creator', 'updator', 'surgeryAppointment']))
|
||||
->deferLoading()
|
||||
->columns([
|
||||
TextColumn::make('first_name')
|
||||
->label(__('patients.fields.first_name'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('last_name')
|
||||
->label(__('patients.fields.last_name'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('hand_phone')
|
||||
->label(__('patients.fields.hand_phone'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('home_phone')
|
||||
->label(__('patients.fields.home_phone'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('creator.name')
|
||||
->label(__('patients.fields.created_by'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('updator.name')
|
||||
->label(__('patients.fields.updated_by'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('patients.fields.updated_at'))
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('refered_by')
|
||||
->label(__('patients.fields.refered_by'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('surgery_before')
|
||||
->label(__('patients.fields.surgery_before'))
|
||||
->width('150px')
|
||||
->extraAttributes(fn (Patient $record): array =>
|
||||
empty(strip_tags($record->surgery_before ?? ''))
|
||||
? ['class' => 'pointer-events-none']
|
||||
: []
|
||||
)
|
||||
->formatStateUsing(function ($state) {
|
||||
$text = strip_tags($state ?? '');
|
||||
if (! $text) {
|
||||
return '-';
|
||||
}
|
||||
if (mb_strlen($text) > 30) {
|
||||
return mb_substr($text, 0, 30)
|
||||
. ' <span class="text-primary-600 text-xs font-semibold cursor-pointer">... بیشتر</span>';
|
||||
}
|
||||
return $text;
|
||||
})
|
||||
->html()
|
||||
->action(
|
||||
Action::make('viewSurgeryBefore')
|
||||
->label(__('patients.fields.surgery_before'))
|
||||
->modalWidth(Width::Large)
|
||||
->modalContent(fn (Patient $record): \Illuminate\Support\HtmlString =>
|
||||
new \Illuminate\Support\HtmlString(
|
||||
'<div dir="rtl" class="prose max-w-none p-4">'
|
||||
. $record->surgery_before
|
||||
. '</div>'
|
||||
)
|
||||
)
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('بستن')
|
||||
),
|
||||
|
||||
TextColumn::make('sergery_after')
|
||||
->label(__('patients.fields.sergery_after'))
|
||||
->width('150px')
|
||||
->extraAttributes(fn (Patient $record): array =>
|
||||
empty(strip_tags($record->sergery_after ?? ''))
|
||||
? ['class' => 'pointer-events-none']
|
||||
: []
|
||||
)
|
||||
->formatStateUsing(function ($state) {
|
||||
$text = strip_tags($state ?? '');
|
||||
if (! $text) {
|
||||
return '-';
|
||||
}
|
||||
if (mb_strlen($text) > 30) {
|
||||
return mb_substr($text, 0, 30)
|
||||
. ' <span class="text-primary-600 text-xs font-semibold cursor-pointer">... بیشتر</span>';
|
||||
}
|
||||
return $text;
|
||||
})
|
||||
->html()
|
||||
->action(
|
||||
Action::make('viewSergeryAfter')
|
||||
->label(__('patients.fields.sergery_after'))
|
||||
->modalWidth(Width::Large)
|
||||
->modalContent(fn (Patient $record): \Illuminate\Support\HtmlString =>
|
||||
new \Illuminate\Support\HtmlString(
|
||||
'<div dir="rtl" class="prose max-w-none p-4">'
|
||||
. $record->sergery_after
|
||||
. '</div>'
|
||||
)
|
||||
)
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('بستن')
|
||||
),
|
||||
|
||||
TextColumn::make('surgeryAppointment.surgery_date')
|
||||
->label(__('patients.fields.surgery_appointment'))
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->icon(fn ($state) => $state ? 'heroicon-o-check-circle' : 'heroicon-o-clock')
|
||||
->iconColor(fn ($state) => $state ? 'success' : 'gray')
|
||||
->placeholder('-'),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->recordActions([
|
||||
Action::make('surgeryAppointment')
|
||||
->label(__('patients.actions.surgery_appointment'))
|
||||
->icon('heroicon-o-calendar-days')
|
||||
->iconButton()
|
||||
->color(fn (Patient $record) => $record->surgeryAppointment ? 'success' : 'info')
|
||||
->modalWidth(Width::Large)
|
||||
->fillForm(function (Patient $record): array {
|
||||
$appointment = $record->surgeryAppointment;
|
||||
|
||||
if ($appointment) {
|
||||
$surgeryDate = $appointment->surgery_date;
|
||||
return [
|
||||
'surgery_date' => $surgeryDate?->format('Y-m-d'),
|
||||
'surgery_time' => $surgeryDate?->format('H:i'),
|
||||
'surgery_center_id' => $appointment->surgery_center_id,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'surgery_date' => null,
|
||||
'surgery_time' => '09:00',
|
||||
'surgery_center_id' => null,
|
||||
];
|
||||
})
|
||||
->form([
|
||||
\Filament\Forms\Components\Placeholder::make('patient_name')
|
||||
->label(__('patients.fields.full_name'))
|
||||
->content(fn (Patient $record) => $record->full_name),
|
||||
|
||||
Grid::make(2)
|
||||
->schema([
|
||||
DatePicker::make('surgery_date')
|
||||
->label(__('patients.fields.surgery_date'))
|
||||
->jalali()
|
||||
->minDate(today())
|
||||
->required(),
|
||||
|
||||
TimePicker::make('surgery_time')
|
||||
->label(__('patients.fields.surgery_time'))
|
||||
->native(false)
|
||||
->seconds(false)
|
||||
->minutesStep(15),
|
||||
]),
|
||||
|
||||
Select::make('surgery_center_id')
|
||||
->label(__('patients.fields.surgery_center'))
|
||||
->options(fn () => SurgeryCenter::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->required(),
|
||||
])
|
||||
->modalSubmitAction(false)
|
||||
->extraModalFooterActions(fn (Action $action): array => [
|
||||
$action->makeModalSubmitAction('saveAppointment', arguments: ['save' => true])
|
||||
->label(__('patients.actions.save_appointment'))
|
||||
->color('primary')
|
||||
->icon('heroicon-o-check')
|
||||
->visible(fn (Patient $record) => $record->surgeryAppointment === null),
|
||||
|
||||
Action::make('printAdmission')
|
||||
->label(__('patients.actions.print_admission'))
|
||||
->color('warning')
|
||||
->icon('heroicon-o-printer')
|
||||
->url(fn (Patient $record) => route('surgery-appointment.print.admission', $record), shouldOpenInNewTab: true)
|
||||
->visible(fn (Patient $record) => $record->surgeryAppointment !== null),
|
||||
|
||||
$action->makeModalSubmitAction('deleteAppointment', arguments: ['delete' => true])
|
||||
->label(__('patients.actions.delete_appointment'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-trash')
|
||||
->visible(fn (Patient $record) => $record->surgeryAppointment !== null),
|
||||
])
|
||||
->action(function (Patient $record, array $data, array $arguments, Action $action): void {
|
||||
if (! empty($arguments['delete'])) {
|
||||
SurgeryAppointment::where('patient_id', $record->id)->delete();
|
||||
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title(__('patients.actions.appointment_deleted'))
|
||||
->success()
|
||||
->send();
|
||||
return;
|
||||
}
|
||||
$dateStr = $data['surgery_date'];
|
||||
$timeStr = $data['surgery_time'] ?? '00:00';
|
||||
$surgeryDateTime = \Carbon\Carbon::parse($dateStr . ' ' . $timeStr);
|
||||
$jalaliDate = Jalalian::fromCarbon($surgeryDateTime)->format('Y/m/d');
|
||||
|
||||
$appointment = SurgeryAppointment::updateOrCreate(
|
||||
['patient_id' => $record->id],
|
||||
[
|
||||
'surgery_date' => $surgeryDateTime,
|
||||
'surgery_center_id' => $data['surgery_center_id'],
|
||||
]
|
||||
);
|
||||
|
||||
try {
|
||||
$response = \Illuminate\Support\Facades\Http::timeout(30)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->post(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/surgery-reminder', [
|
||||
'phoneNumber' => $record->hand_phone ?? '',
|
||||
'patientName' => $record->full_name,
|
||||
'surgeryDate' => $jalaliDate,
|
||||
'appointmentId' => $appointment->id,
|
||||
'patientId' => $record->id,
|
||||
'surgeryDateFull' => $surgeryDateTime->toDateTimeString(),
|
||||
]);
|
||||
|
||||
if (! ($response->json('success') ?? false)) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.api_failed') . ': ' . ($response->json('message') ?? ''))
|
||||
->warning()
|
||||
->send();
|
||||
} else {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.sms_sent'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\Illuminate\Support\Facades\Log::warning('Surgery appointment API failed', [
|
||||
'patient_id' => $record->id,
|
||||
'appointment_id' => $appointment->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.api_failed') . ': ' . $e->getMessage())
|
||||
->warning()
|
||||
->send();
|
||||
}
|
||||
|
||||
$record->refresh();
|
||||
$action->halt();
|
||||
}),
|
||||
|
||||
Action::make('prescription')
|
||||
->label(__('prescriptions.actions.create'))
|
||||
->icon('heroicon-o-document-text')
|
||||
->iconButton()
|
||||
->color('warning')
|
||||
->slideOver()
|
||||
->fillForm(function (Patient $record): array {
|
||||
$prescription = Prescription::where('patient', $record->id)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($prescription) {
|
||||
$medicationIds = $prescription->medication_ids ?? [];
|
||||
$content = $prescription->content ?? '';
|
||||
} else {
|
||||
$defaultMeds = Medication::where('is_active', true)
|
||||
->where('is_default', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
$medicationIds = $defaultMeds->pluck('id')->toArray();
|
||||
$content = $defaultMeds->isNotEmpty()
|
||||
? '<ul>' . $defaultMeds->map(fn ($m) =>
|
||||
'<li>' . implode(' ', array_filter([
|
||||
$m->dosage_form,
|
||||
$m->name,
|
||||
$m->strength,
|
||||
$m->quantity ? '#' . $m->quantity : null,
|
||||
$m->timing,
|
||||
])) . '</li>'
|
||||
)->implode('') . '</ul>'
|
||||
: '';
|
||||
}
|
||||
|
||||
return [
|
||||
'medication_ids' => $medicationIds,
|
||||
'medication_order' => $medicationIds,
|
||||
'content' => $content,
|
||||
'issue_date' => now()->toDateString(),
|
||||
];
|
||||
})
|
||||
->form([
|
||||
Grid::make(2)
|
||||
->schema([
|
||||
\Filament\Forms\Components\Placeholder::make('patient_name')
|
||||
->label(__('prescriptions.fields.patient'))
|
||||
->content(fn (Patient $record) => $record->full_name),
|
||||
|
||||
\Filament\Forms\Components\DatePicker::make('issue_date')
|
||||
->label(__('prescriptions.fields.issue_date'))
|
||||
->jalali()
|
||||
->required(),
|
||||
]),
|
||||
|
||||
\Filament\Forms\Components\Hidden::make('medication_order'),
|
||||
|
||||
\Filament\Forms\Components\CheckboxList::make('medication_ids')
|
||||
->label(__('prescriptions.fields.medication_ids'))
|
||||
->options(fn () => Medication::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->mapWithKeys(fn ($m) => [
|
||||
$m->id => implode(' ', array_filter([
|
||||
$m->dosage_form,
|
||||
$m->name,
|
||||
$m->strength,
|
||||
$m->quantity ? '#' . $m->quantity : null,
|
||||
$m->timing,
|
||||
])),
|
||||
])
|
||||
->toArray()
|
||||
)
|
||||
->columns(2)
|
||||
->live()
|
||||
->afterStateUpdated(function (array $state, callable $set, callable $get) {
|
||||
$currentOrder = array_map('intval', (array) ($get('medication_order') ?: []));
|
||||
$state = array_map('intval', $state);
|
||||
|
||||
$newOrder = array_values(
|
||||
array_filter($currentOrder, fn ($id) => in_array($id, $state))
|
||||
);
|
||||
|
||||
foreach ($state as $id) {
|
||||
if (! in_array($id, $newOrder)) {
|
||||
$newOrder[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
$set('medication_order', $newOrder);
|
||||
|
||||
if (empty($newOrder)) {
|
||||
$set('content', null);
|
||||
return;
|
||||
}
|
||||
|
||||
$medications = Medication::whereIn('id', $newOrder)->get()->keyBy('id');
|
||||
$html = '<ul>' . collect($newOrder)
|
||||
->map(fn ($id) => isset($medications[$id])
|
||||
? '<li>' . implode(' ', array_filter([
|
||||
$medications[$id]->dosage_form,
|
||||
$medications[$id]->name,
|
||||
$medications[$id]->strength,
|
||||
$medications[$id]->quantity ? '#' . $medications[$id]->quantity : null,
|
||||
$medications[$id]->timing,
|
||||
])) . '</li>'
|
||||
: ''
|
||||
)
|
||||
->filter()
|
||||
->implode('') . '</ul>';
|
||||
|
||||
$set('content', $html);
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
\Filament\Forms\Components\RichEditor::make('content')
|
||||
->label(__('prescriptions.fields.content'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->modalSubmitAction(false)
|
||||
->extraModalFooterActions(fn (Action $action): array => [
|
||||
$action->makeModalSubmitAction('printPrescription', arguments: ['printType' => 'prescription'])
|
||||
->label(__('prescriptions.actions.save_and_print'))
|
||||
->color('info')
|
||||
->icon('heroicon-o-printer'),
|
||||
|
||||
$action->makeModalSubmitAction('printAdmission', arguments: ['printType' => 'admission'])
|
||||
->label(__('prescriptions.actions.print_admission'))
|
||||
->color('warning')
|
||||
->icon('heroicon-o-printer')
|
||||
->visible(fn (Patient $record) => $record->surgeryAppointment !== null),
|
||||
|
||||
$action->makeModalSubmitAction('printLab', arguments: ['printType' => 'lab'])
|
||||
->label(__('prescriptions.actions.print_lab'))
|
||||
->color('gray')
|
||||
->icon('heroicon-o-beaker'),
|
||||
])
|
||||
->action(function (Patient $record, array $data, array $arguments, $livewire): void {
|
||||
$prescription = Prescription::updateOrCreate(
|
||||
['patient' => $record->id],
|
||||
[
|
||||
'doctor' => auth()->user()?->name,
|
||||
'issue_date' => $data['issue_date'] ?? '',
|
||||
'issue_datetime' => now(),
|
||||
'medication_ids' => $data['medication_order'] ?? $data['medication_ids'] ?? [],
|
||||
'content' => $data['content'] ?? '',
|
||||
]
|
||||
);
|
||||
|
||||
$printType = $arguments['printType'] ?? null;
|
||||
|
||||
if ($printType === 'prescription') {
|
||||
$livewire->redirect(route('prescription.print', $prescription));
|
||||
} elseif ($printType === 'admission') {
|
||||
$livewire->redirect(route('prescription.print.admission', $prescription));
|
||||
} elseif ($printType === 'lab') {
|
||||
$livewire->redirect(route('prescription.print', ['prescription' => $prescription, 'type' => 'lab']));
|
||||
}
|
||||
})
|
||||
->successNotificationTitle(__('prescriptions.actions.saved')),
|
||||
|
||||
Action::make('viewFiles')
|
||||
->label(__('patients.actions.view_files'))
|
||||
->icon('heroicon-o-paper-clip')
|
||||
->iconButton()
|
||||
->color('warning')
|
||||
->visible(fn (Patient $record): bool =>
|
||||
! empty(array_filter([
|
||||
$record->photos_before,
|
||||
$record->photos_after,
|
||||
$record->videos,
|
||||
$record->audio_files,
|
||||
]))
|
||||
)
|
||||
->modalWidth(Width::Large)
|
||||
->fillForm(fn (Patient $record): array => [
|
||||
'photos_before' => $record->photos_before ?? [],
|
||||
'photos_after' => $record->photos_after ?? [],
|
||||
'videos' => $record->videos ?? [],
|
||||
'audio_files' => $record->audio_files ?? [],
|
||||
])
|
||||
->schema([
|
||||
Grid::make(2)
|
||||
->schema([
|
||||
FileUpload::make('photos_before')
|
||||
->label(__('patients.fields.photos_before'))
|
||||
->multiple()
|
||||
->image()
|
||||
->imagePreviewHeight('150')
|
||||
->disk('public')
|
||||
->directory('surgery_photos/before')
|
||||
->downloadable()
|
||||
->openable()
|
||||
->disabled()
|
||||
->extraAttributes(['data-hide-dropzone' => 'true']),
|
||||
|
||||
FileUpload::make('photos_after')
|
||||
->label(__('patients.fields.photos_after'))
|
||||
->multiple()
|
||||
->image()
|
||||
->imagePreviewHeight('150')
|
||||
->disk('public')
|
||||
->directory('surgery_photos/after')
|
||||
->downloadable()
|
||||
->openable()
|
||||
->disabled()
|
||||
->extraAttributes(['data-hide-dropzone' => 'true']),
|
||||
|
||||
FileUpload::make('videos')
|
||||
->label(__('patients.fields.videos'))
|
||||
->multiple()
|
||||
->disk('public')
|
||||
->directory('surgery_photos/videos')
|
||||
->acceptedFileTypes(['video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska', 'video/webm', 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/aac'])
|
||||
->downloadable()
|
||||
->disabled()
|
||||
->extraAttributes(['data-hide-dropzone' => 'true']),
|
||||
|
||||
FileUpload::make('audio_files')
|
||||
->label(__('patients.fields.audio_files'))
|
||||
->multiple()
|
||||
->disk('public')
|
||||
->directory('audio_files')
|
||||
->acceptedFileTypes(['audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/aac'])
|
||||
->downloadable()
|
||||
->disabled()
|
||||
->extraAttributes(['data-hide-dropzone' => 'true']),
|
||||
]),
|
||||
])
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('بستن'),
|
||||
|
||||
EditAction::make()->iconButton(),
|
||||
DeleteAction::make()->iconButton(),
|
||||
], position: \Filament\Tables\Enums\RecordActionsPosition::BeforeCells)
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('patients.fields.first_name'),
|
||||
__('patients.fields.last_name'),
|
||||
__('patients.fields.icno'),
|
||||
__('patients.fields.gender'),
|
||||
__('patients.fields.hand_phone'),
|
||||
__('patients.fields.insurance'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->first_name,
|
||||
$record->last_name,
|
||||
$record->icno,
|
||||
$record->gender,
|
||||
$record->hand_phone,
|
||||
$record->insurance,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'patients-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Patient::orderBy('last_name')->orderBy('first_name')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('patients.fields.first_name'),
|
||||
__('patients.fields.last_name'),
|
||||
__('patients.fields.icno'),
|
||||
__('patients.fields.gender'),
|
||||
__('patients.fields.hand_phone'),
|
||||
__('patients.fields.insurance'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->first_name,
|
||||
$record->last_name,
|
||||
$record->icno,
|
||||
$record->gender,
|
||||
$record->hand_phone,
|
||||
$record->insurance,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'patients-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Patient::orderBy('last_name')->orderBy('first_name')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.patients-pdf',
|
||||
['records' => $records],
|
||||
'patients-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
VisitsRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPatients::route('/'),
|
||||
'create' => Pages\CreatePatient::route('/create'),
|
||||
'edit' => Pages\EditPatient::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class CreatePatient extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PatientResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class EditPatient extends EditRecord
|
||||
{
|
||||
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(),
|
||||
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PatientResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class ListPatients extends ListRecords
|
||||
{
|
||||
protected static string $resource = PatientResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::Full;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PatientResource\RelationManagers;
|
||||
|
||||
use App\Models\Doctor;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\Treatment;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\TimePicker;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\Summarizers\Sum;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class VisitsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'visits';
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('navigation.visits.title');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make(__('visits.sections.visit_info'))
|
||||
->schema([
|
||||
DatePicker::make('visit_date')
|
||||
->label(__('visits.fields.visit_date'))
|
||||
->jalali()
|
||||
->default(now())
|
||||
->required(),
|
||||
|
||||
TimePicker::make('visit_time')
|
||||
->label(__('visits.fields.visit_time'))
|
||||
->nullable()
|
||||
->native(false)
|
||||
->seconds(false)
|
||||
->minutesStep(15),
|
||||
|
||||
Select::make('doctor')
|
||||
->label(__('visits.fields.doctor'))
|
||||
->options(fn () => Doctor::all()->mapWithKeys(
|
||||
fn ($d) => [$d->id => $d->full_name]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable(),
|
||||
|
||||
Select::make('treatment')
|
||||
->label(__('visits.fields.treatment'))
|
||||
->options(fn () => Treatment::all()->mapWithKeys(
|
||||
fn ($t) => [$t->id => ($t->treatment_name ?: $t->treatment_type)]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, callable $set) {
|
||||
if ($state) {
|
||||
$treatment = Treatment::find($state);
|
||||
if ($treatment && $treatment->treatment_cost > 0) {
|
||||
$set('treatment_cost', $treatment->treatment_cost);
|
||||
}
|
||||
}
|
||||
}),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('visits.sections.financial_info'))
|
||||
->schema([
|
||||
TextInput::make('treatment_cost')
|
||||
->label(__('visits.fields.treatment_cost'))
|
||||
->numeric()
|
||||
->nullable()
|
||||
->suffix(__('visits.fields.currency')),
|
||||
|
||||
TextInput::make('paid_amount')
|
||||
->label(__('visits.fields.paid_amount'))
|
||||
->numeric()
|
||||
->nullable()
|
||||
->suffix(__('visits.fields.currency')),
|
||||
|
||||
Select::make('payment_type')
|
||||
->label(__('visits.fields.payment_type'))
|
||||
->options(fn () => PaymentType::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->payment_type]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable(),
|
||||
])
|
||||
->columns(3)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('visits.sections.treatment_details'))
|
||||
->schema([
|
||||
Textarea::make('treatment_description')
|
||||
->label(__('visits.fields.treatment_description'))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('visit_date')
|
||||
->columns([
|
||||
TextColumn::make('visit_date')
|
||||
->label(__('visits.fields.visit_date'))
|
||||
->sortable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return \Morilog\Jalali\Jalalian::fromCarbon($date)->format('Y/m/d');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('doctorRecord.first_name')
|
||||
->label(__('visits.fields.doctor'))
|
||||
->formatStateUsing(fn ($record) => $record->doctorRecord?->full_name ?? '-')
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('treatmentRecord.treatment_name')
|
||||
->label(__('visits.fields.treatment'))
|
||||
->formatStateUsing(fn ($record) => $record->treatmentRecord
|
||||
? ($record->treatmentRecord->treatment_name ?: $record->treatmentRecord->treatment_type)
|
||||
: '-')
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('treatment_cost')
|
||||
->label(__('visits.fields.treatment_cost'))
|
||||
->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-')
|
||||
->placeholder('-')
|
||||
->summarize(
|
||||
Sum::make()
|
||||
->label(__('visits.fields.treatment_cost'))
|
||||
->formatStateUsing(fn ($state) => number_format((int) ($state ?? 0)))
|
||||
),
|
||||
|
||||
TextColumn::make('paid_amount')
|
||||
->label(__('visits.fields.paid_amount'))
|
||||
->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-')
|
||||
->placeholder('-')
|
||||
->summarize(
|
||||
Sum::make()
|
||||
->label(__('visits.fields.paid_amount'))
|
||||
->formatStateUsing(fn ($state) => number_format((int) ($state ?? 0)))
|
||||
),
|
||||
|
||||
TextColumn::make('remained_amount')
|
||||
->label(__('visits.fields.remained_amount'))
|
||||
->state(fn ($record) => $record->remained_amount)
|
||||
->formatStateUsing(fn ($state) => number_format((int) $state))
|
||||
->color(fn ($record) => $record->remained_amount > 0 ? 'danger' : 'success'),
|
||||
|
||||
TextColumn::make('paymentTypeRecord.payment_type')
|
||||
->label(__('visits.fields.payment_type'))
|
||||
->placeholder('-'),
|
||||
])
|
||||
->defaultSort('visit_date', 'desc')
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->label(__('visits.actions.create'))
|
||||
->icon('heroicon-o-plus')
|
||||
->slideOver()
|
||||
->modalWidth('4xl'),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->slideOver()
|
||||
->modalWidth('4xl'),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PaymentTypeResource\Pages;
|
||||
use App\Models\PaymentType;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class PaymentTypeResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PaymentType::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'payment_type';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-credit-card';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.clinic');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.payment_types.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.payment_types.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.payment_types.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('payment_type')
|
||||
->label(__('payment_types.fields.payment_type'))
|
||||
->required()
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('account_no')
|
||||
->label(__('payment_types.fields.account_no'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('pos_no')
|
||||
->label(__('payment_types.fields.pos_no'))
|
||||
->maxLength(50),
|
||||
|
||||
Textarea::make('description')
|
||||
->label(__('payment_types.fields.description'))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('payment_type')
|
||||
->label(__('payment_types.fields.payment_type'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('account_no')
|
||||
->label(__('payment_types.fields.account_no'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('pos_no')
|
||||
->label(__('payment_types.fields.pos_no'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('description')
|
||||
->label(__('payment_types.fields.description'))
|
||||
->limit(50)
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('payment_types.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('payment_types.fields.payment_type'),
|
||||
__('payment_types.fields.account_no'),
|
||||
__('payment_types.fields.pos_no'),
|
||||
__('payment_types.fields.description'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->payment_type,
|
||||
$record->account_no,
|
||||
$record->pos_no,
|
||||
$record->description,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'payment-types-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = PaymentType::all();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('payment_types.fields.payment_type'),
|
||||
__('payment_types.fields.account_no'),
|
||||
__('payment_types.fields.pos_no'),
|
||||
__('payment_types.fields.description'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->payment_type,
|
||||
$record->account_no,
|
||||
$record->pos_no,
|
||||
$record->description,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'payment-types-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = PaymentType::all();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.payment-types-pdf',
|
||||
['records' => $records],
|
||||
'payment-types-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPaymentTypes::route('/'),
|
||||
'create' => Pages\CreatePaymentType::route('/create'),
|
||||
'edit' => Pages\EditPaymentType::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PaymentTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTypeResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePaymentType extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PaymentTypeResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PaymentTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTypeResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPaymentType extends EditRecord
|
||||
{
|
||||
protected static string $resource = PaymentTypeResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PaymentTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PaymentTypeResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPaymentTypes extends ListRecords
|
||||
{
|
||||
protected static string $resource = PaymentTypeResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
use App\Filament\Resources\PrescriptionResource\Pages;
|
||||
use App\Models\Medication;
|
||||
use App\Models\Patient;
|
||||
use App\Models\Prescription;
|
||||
use App\Services\PdfService;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class PrescriptionResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Prescription::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'doctor';
|
||||
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return ['doctor', 'patientRecord.first_name', 'patientRecord.last_name'];
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-document-text';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.patients');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.prescriptions.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.prescriptions.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.prescriptions.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make(__('prescriptions.sections.info'))
|
||||
->schema([
|
||||
Select::make('patient')
|
||||
->label(__('prescriptions.fields.patient'))
|
||||
->options(fn () => Patient::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->full_name]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable(),
|
||||
|
||||
DatePicker::make('issue_date')
|
||||
->label(__('prescriptions.fields.issue_date'))
|
||||
->jalali()
|
||||
->default(now()),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('prescriptions.sections.medications'))
|
||||
->schema([
|
||||
CheckboxList::make('medication_ids')
|
||||
->label(__('prescriptions.fields.medication_ids'))
|
||||
->options(fn () => Medication::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->mapWithKeys(fn ($m) => [
|
||||
$m->id => implode(' ', array_filter([
|
||||
$m->dosage_form,
|
||||
$m->name,
|
||||
$m->strength,
|
||||
$m->quantity ? '#' . $m->quantity : null,
|
||||
$m->timing,
|
||||
])),
|
||||
])
|
||||
->toArray()
|
||||
)
|
||||
->columns(3)
|
||||
->live()
|
||||
->afterStateUpdated(function (array $state, callable $set) {
|
||||
if (empty($state)) {
|
||||
$set('content', null);
|
||||
return;
|
||||
}
|
||||
$medications = Medication::whereIn('id', $state)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
$html = '<ul>' . $medications->map(fn ($m) =>
|
||||
'<li>' . implode(' ', array_filter([
|
||||
$m->dosage_form,
|
||||
$m->name,
|
||||
$m->strength,
|
||||
$m->quantity ? '#' . $m->quantity : null,
|
||||
$m->timing,
|
||||
])) . '</li>'
|
||||
)->implode('') . '</ul>';
|
||||
$set('content', $html);
|
||||
})
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('prescriptions.sections.content'))
|
||||
->schema([
|
||||
RichEditor::make('content')
|
||||
->label(__('prescriptions.fields.content'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('createAndPrint')
|
||||
->label(__('prescriptions.actions.save_and_print'))
|
||||
->color('warning')
|
||||
->icon('heroicon-o-printer')
|
||||
->action(function ($livewire) {
|
||||
$livewire->printType = 'prescription';
|
||||
$livewire->create();
|
||||
})
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->modifyQueryUsing(fn ($query) => $query->with(['patientRecord', 'creator']))
|
||||
->deferLoading()
|
||||
->columns([
|
||||
TextColumn::make('patientRecord.first_name')
|
||||
->label(__('prescriptions.fields.patient'))
|
||||
->formatStateUsing(fn ($record) => $record->patientRecord?->full_name ?? '-')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('doctor')
|
||||
->label(__('prescriptions.fields.doctor'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('issue_date')
|
||||
->label(__('prescriptions.fields.issue_date'))
|
||||
->sortable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('content')
|
||||
->label(__('prescriptions.fields.content'))
|
||||
->formatStateUsing(fn ($state) => strip_tags($state ?? ''))
|
||||
->limit(80)
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('creator.name')
|
||||
->label(__('prescriptions.fields.created_by'))
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('prescriptions.fields.created_at'))
|
||||
->sortable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
}),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->recordActions([
|
||||
Action::make('printPrescription')
|
||||
->label(__('prescriptions.actions.print_prescription'))
|
||||
->icon('heroicon-o-printer')
|
||||
->color('info')
|
||||
->url(fn ($record) => route('prescription.print', $record))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Action::make('printAdmission')
|
||||
->label(__('prescriptions.actions.print_admission'))
|
||||
->icon('heroicon-o-printer')
|
||||
->color('warning')
|
||||
->url(fn ($record) => route('prescription.print.admission', $record))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Action::make('printLab')
|
||||
->label(__('prescriptions.actions.print_lab'))
|
||||
->icon('heroicon-o-beaker')
|
||||
->color('gray')
|
||||
->url(fn ($record) => route('prescription.print', ['prescription' => $record, 'type' => 'lab']))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('prescriptions.fields.patient'),
|
||||
__('prescriptions.fields.doctor'),
|
||||
__('prescriptions.fields.issue_date'),
|
||||
__('prescriptions.fields.content'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->patientRecord?->full_name ?? '',
|
||||
$record->doctor,
|
||||
$record->issue_date,
|
||||
strip_tags($record->content ?? ''),
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'prescriptions-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Prescription::with('patientRecord')->orderBy('created_at', 'desc')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('prescriptions.fields.patient'),
|
||||
__('prescriptions.fields.doctor'),
|
||||
__('prescriptions.fields.issue_date'),
|
||||
__('prescriptions.fields.content'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->patientRecord?->full_name ?? '',
|
||||
$record->doctor,
|
||||
$record->issue_date,
|
||||
strip_tags($record->content ?? ''),
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'prescriptions-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Prescription::with('patientRecord')->orderBy('created_at', 'desc')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.prescriptions-pdf',
|
||||
['records' => $records],
|
||||
'prescriptions-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPrescriptions::route('/'),
|
||||
'create' => Pages\CreatePrescription::route('/create'),
|
||||
'edit' => Pages\EditPrescription::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PrescriptionResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PrescriptionResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class CreatePrescription extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PrescriptionResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
public string $printType = '';
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$data['doctor'] = auth()->user()?->name;
|
||||
$data['issue_datetime'] = now();
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
$record = $this->getRecord();
|
||||
|
||||
if ($this->printType === 'prescription') {
|
||||
return route('prescription.print', $record);
|
||||
}
|
||||
|
||||
if ($this->printType === 'lab') {
|
||||
return route('prescription.print', ['prescription' => $record, 'type' => 'lab']);
|
||||
}
|
||||
|
||||
if ($this->printType === 'admission') {
|
||||
return route('prescription.print.admission', $record);
|
||||
}
|
||||
|
||||
return static::getResource()::getUrl('index');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PrescriptionResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PrescriptionResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class EditPrescription extends EditRecord
|
||||
{
|
||||
protected static string $resource = PrescriptionResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
$data['doctor'] = auth()->user()?->name;
|
||||
$data['issue_datetime'] = now();
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('printPrescription')
|
||||
->label(__('prescriptions.actions.print_prescription'))
|
||||
->icon('heroicon-o-printer')
|
||||
->color('info')
|
||||
->url(fn () => route('prescription.print', $this->getRecord()))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Action::make('printAdmission')
|
||||
->label(__('prescriptions.actions.print_admission'))
|
||||
->icon('heroicon-o-printer')
|
||||
->color('warning')
|
||||
->visible(fn () => $this->getRecord()->patientRecord?->surgeryAppointment !== null)
|
||||
->url(fn () => route('prescription.print.admission', $this->getRecord()))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Action::make('printLab')
|
||||
->label(__('prescriptions.actions.print_lab'))
|
||||
->icon('heroicon-o-beaker')
|
||||
->color('gray')
|
||||
->url(fn () => route('prescription.print', ['prescription' => $this->getRecord(), 'type' => 'lab']))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\PrescriptionResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PrescriptionResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPrescriptions extends ListRecords
|
||||
{
|
||||
protected static string $resource = PrescriptionResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\RoleResource\Pages;
|
||||
use App\Services\PdfService;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use BezhanSalleh\FilamentShield\Resources\Roles\RoleResource as ShieldRoleResource;
|
||||
use BezhanSalleh\FilamentShield\Support\Utils;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Unique;
|
||||
use Livewire\Component as Livewire;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleResource extends ShieldRoleResource
|
||||
{
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.roles.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.roles.title');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.roles.title');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.settings');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('filament-shield::filament-shield.field.name'))
|
||||
->unique(
|
||||
ignoreRecord: true,
|
||||
modifyRuleUsing: fn (Unique $rule): Unique => Utils::isTenancyEnabled()
|
||||
? $rule->where(Utils::getTenantModelForeignKey(), Filament::getTenant()?->id)
|
||||
: $rule
|
||||
)
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('guard_name')
|
||||
->label(__('filament-shield::filament-shield.field.guard_name'))
|
||||
->default(Utils::getFilamentAuthGuard())
|
||||
->nullable()
|
||||
->maxLength(255),
|
||||
|
||||
Select::make(config('permission.column_names.team_foreign_key'))
|
||||
->label(__('filament-shield::filament-shield.field.team'))
|
||||
->placeholder(__('filament-shield::filament-shield.field.team.placeholder'))
|
||||
->default(Filament::getTenant()?->id)
|
||||
->options(fn (): array => in_array(Utils::getTenantModel(), [null, '', '0'], true) ? [] : Utils::getTenantModel()::pluck('name', 'id')->toArray())
|
||||
->visible(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled())
|
||||
->dehydrated(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled()),
|
||||
|
||||
static::getSelectAllFormComponent(),
|
||||
])
|
||||
->columns(['sm' => 2, 'lg' => 3])
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
static::getShieldFormComponents(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->weight(FontWeight::Medium)
|
||||
->label(__('filament-shield::filament-shield.column.name'))
|
||||
->formatStateUsing(fn (string $state): string => Str::headline($state))
|
||||
->searchable(),
|
||||
TextColumn::make('guard_name')
|
||||
->badge()
|
||||
->color('warning')
|
||||
->label(__('filament-shield::filament-shield.column.guard_name')),
|
||||
TextColumn::make('team.name')
|
||||
->default('Global')
|
||||
->badge()
|
||||
->color(fn (mixed $state): string => str($state)->contains('Global') ? 'gray' : 'primary')
|
||||
->label(__('filament-shield::filament-shield.column.team'))
|
||||
->searchable()
|
||||
->visible(fn (): bool => static::shield()->isCentralApp() && Utils::isTenancyEnabled()),
|
||||
TextColumn::make('permissions_count')
|
||||
->badge()
|
||||
->label(__('filament-shield::filament-shield.column.permissions'))
|
||||
->counts('permissions')
|
||||
->color('primary'),
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('filament-shield::filament-shield.column.updated_at'))
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
}),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->paginationPageOptions([5, 10, 25, 50, 100])
|
||||
->defaultPaginationPageOption(25)
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('filament-shield::filament-shield.column.name'),
|
||||
__('filament-shield::filament-shield.column.guard_name'),
|
||||
__('filament-shield::filament-shield.column.permissions'),
|
||||
__('filament-shield::filament-shield.column.updated_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->guard_name,
|
||||
$record->permissions_count ?? $record->permissions()->count(),
|
||||
$record->updated_at?->format('Y-m-d H:i:s'),
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'roles-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Role::with('permissions')->withCount('permissions')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('filament-shield::filament-shield.column.name'),
|
||||
__('filament-shield::filament-shield.column.guard_name'),
|
||||
__('filament-shield::filament-shield.column.permissions'),
|
||||
__('filament-shield::filament-shield.column.updated_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->guard_name,
|
||||
$record->permissions_count,
|
||||
$record->updated_at?->format('Y-m-d H:i:s'),
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'roles-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Role::with('permissions')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.roles-pdf',
|
||||
['records' => $records],
|
||||
'roles-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListRoles::route('/'),
|
||||
'create' => Pages\CreateRole::route('/create'),
|
||||
'view' => Pages\ViewRole::route('/{record}'),
|
||||
'edit' => Pages\EditRole::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function toggleSelectAllViaEntities(Livewire $livewire, Set $set): void
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$entitiesStates = collect($livewire->form->getFlatComponents())
|
||||
->filter(fn (mixed $component): bool => $component instanceof Component)
|
||||
->reduce(function (mixed $counts, Component $component) {
|
||||
if ($component instanceof CheckboxList) {
|
||||
$counts[$component->getName()] = count(array_keys($component->getOptions())) === count(collect($component->getState())->values()->unique()->toArray());
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}, collect())
|
||||
->values();
|
||||
|
||||
if ($entitiesStates->containsStrict(false)) {
|
||||
$set('select_all', false);
|
||||
} else {
|
||||
$set('select_all', true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function toggleEntitiesViaSelectAll(Livewire $livewire, Set $set, bool $state): void
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$entitiesComponents = collect($livewire->form->getFlatComponents())
|
||||
->filter(fn (mixed $component): bool => $component instanceof CheckboxList);
|
||||
|
||||
if ($state) {
|
||||
$entitiesComponents->each(function (CheckboxList $component) use ($set): void {
|
||||
$set($component->getName(), array_keys($component->getOptions()));
|
||||
});
|
||||
} else {
|
||||
$entitiesComponents->each(fn (CheckboxList $component) => $component->state([]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RoleResource;
|
||||
use BezhanSalleh\FilamentShield\Resources\Roles\Pages\CreateRole as ShieldCreateRole;
|
||||
|
||||
class CreateRole extends ShieldCreateRole
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RoleResource;
|
||||
use BezhanSalleh\FilamentShield\Resources\Roles\Pages\EditRole as ShieldEditRole;
|
||||
use Filament\Actions\DeleteAction;
|
||||
|
||||
class EditRole extends ShieldEditRole
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RoleResource;
|
||||
use BezhanSalleh\FilamentShield\Resources\Roles\Pages\ListRoles as ShieldListRoles;
|
||||
|
||||
class ListRoles extends ShieldListRoles
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\RoleResource\Pages;
|
||||
|
||||
use App\Filament\Resources\RoleResource;
|
||||
use BezhanSalleh\FilamentShield\Resources\Roles\Pages\ViewRole as ShieldViewRole;
|
||||
|
||||
class ViewRole extends ShieldViewRole
|
||||
{
|
||||
protected static string $resource = RoleResource::class;
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\SurgeryAppointmentResource\Pages;
|
||||
use App\Models\Patient;
|
||||
use App\Models\SurgeryAppointment;
|
||||
use App\Models\SurgeryCenter;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TimePicker;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class SurgeryAppointmentResource extends Resource
|
||||
{
|
||||
protected static ?string $model = SurgeryAppointment::class;
|
||||
|
||||
protected static bool $isGloballySearchable = false;
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-calendar-days';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.patients');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_appointments.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_appointments.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_appointments.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make(__('surgery_appointments.sections.info'))
|
||||
->schema([
|
||||
Select::make('patient_id')
|
||||
->label(__('surgery_appointments.fields.patient'))
|
||||
->options(fn () => Patient::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->full_name]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->required(),
|
||||
|
||||
Select::make('surgery_center_id')
|
||||
->label(__('surgery_appointments.fields.surgery_center'))
|
||||
->options(fn () => SurgeryCenter::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->required(),
|
||||
|
||||
DatePicker::make('surgery_date_date')
|
||||
->label(__('surgery_appointments.fields.surgery_date'))
|
||||
->jalali()
|
||||
->minDate(today())
|
||||
->required(),
|
||||
|
||||
TimePicker::make('surgery_date_time')
|
||||
->label(__('surgery_appointments.fields.surgery_time'))
|
||||
->native(false)
|
||||
->seconds(false)
|
||||
->minutesStep(15)
|
||||
->default('09:00'),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('patient.first_name')
|
||||
->label(__('surgery_appointments.fields.patient'))
|
||||
->formatStateUsing(fn ($record) => $record->patient?->full_name ?? '-')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('surgeryCenter.name')
|
||||
->label(__('surgery_appointments.fields.surgery_center'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('surgery_date')
|
||||
->label(__('surgery_appointments.fields.surgery_date'))
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('creator.name')
|
||||
->label(__('surgery_appointments.fields.created_by'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('surgery_appointments.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('surgery_date', 'desc')
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListSurgeryAppointments::route('/'),
|
||||
'create' => Pages\CreateSurgeryAppointment::route('/create'),
|
||||
'edit' => Pages\EditSurgeryAppointment::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryAppointmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryAppointmentResource;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class CreateSurgeryAppointment extends CreateRecord
|
||||
{
|
||||
protected static string $resource = SurgeryAppointmentResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$dateStr = $data['surgery_date_date'] ?? '';
|
||||
$timeStr = $data['surgery_date_time'] ?? '09:00';
|
||||
$data['surgery_date'] = \Carbon\Carbon::parse($dateStr . ' ' . $timeStr);
|
||||
|
||||
unset($data['surgery_date_date'], $data['surgery_date_time']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
$record = $this->record;
|
||||
$patient = $record->patient;
|
||||
$surgeryDateTime = $record->getRawOriginal('surgery_date')
|
||||
? \Carbon\Carbon::parse($record->getRawOriginal('surgery_date'))
|
||||
: \Carbon\Carbon::parse($record->surgery_date);
|
||||
$jalaliDate = Jalalian::fromCarbon($surgeryDateTime)->format('Y/m/d');
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->asJson()
|
||||
->post(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/surgery-reminder', [
|
||||
'phoneNumber' => $patient->hand_phone ?? '',
|
||||
'patientName' => $patient->full_name,
|
||||
'surgeryDate' => $jalaliDate,
|
||||
'appointmentId' => $record->id,
|
||||
'patientId' => $patient->id,
|
||||
'surgeryDateFull' => $surgeryDateTime->toDateTimeString(),
|
||||
]);
|
||||
|
||||
if (! ($response->json('success') ?? false)) {
|
||||
Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.api_failed') . ': ' . ($response->json('message') ?? ''))
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Surgery appointment API failed', [
|
||||
'appointment_id' => $record->id,
|
||||
'patient_id' => $patient->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.api_failed') . ': ' . $e->getMessage())
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.sms_sent'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryAppointmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryAppointmentResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class EditSurgeryAppointment extends EditRecord
|
||||
{
|
||||
protected static string $resource = SurgeryAppointmentResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
if (! empty($data['surgery_date'])) {
|
||||
$date = \Carbon\Carbon::parse($data['surgery_date']);
|
||||
$data['surgery_date_date'] = $date->format('Y-m-d');
|
||||
$data['surgery_date_time'] = $date->format('H:i');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
$dateStr = $data['surgery_date_date'] ?? '';
|
||||
$timeStr = $data['surgery_date_time'] ?? '09:00';
|
||||
$data['surgery_date'] = \Carbon\Carbon::parse($dateStr . ' ' . $timeStr);
|
||||
|
||||
unset($data['surgery_date_date'], $data['surgery_date_time']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function beforeSave(): void
|
||||
{
|
||||
$data = $this->form->getState();
|
||||
$dateStr = $data['surgery_date_date'] ?? '';
|
||||
$timeStr = $data['surgery_date_time'] ?? '09:00';
|
||||
$surgeryDateTime = \Carbon\Carbon::parse($dateStr . ' ' . $timeStr);
|
||||
$jalaliDate = Jalalian::fromCarbon($surgeryDateTime)->format('Y/m/d');
|
||||
|
||||
$patient = $this->getRecord()->patient;
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)
|
||||
->withHeader('X-API-Key', \App\Models\OsurgInitial::val('sms_api_key', config('sms.api_key')))
|
||||
->asJson()
|
||||
->post(\App\Models\OsurgInitial::val('sms_api_url', config('sms.api_url')) . '/surgery-reminder', [
|
||||
'phoneNumber' => $patient->hand_phone ?? '',
|
||||
'patientName' => $patient->full_name,
|
||||
'surgeryDate' => $jalaliDate,
|
||||
'appointmentId' => $this->getRecord()->id,
|
||||
'patientId' => $patient->id,
|
||||
'surgeryDateFull' => $surgeryDateTime->toDateTimeString(),
|
||||
]);
|
||||
|
||||
if (! ($response->json('success') ?? false)) {
|
||||
Notification::make()
|
||||
->title(__('patients.actions.api_failed'))
|
||||
->body($response->json('message') ?? '')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
$this->halt();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Surgery appointment API failed', [
|
||||
'patient_id' => $patient->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title(__('patients.actions.api_failed'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
$this->halt();
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
Notification::make()
|
||||
->title(__('patients.actions.appointment_saved'))
|
||||
->body(__('patients.actions.sms_sent'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryAppointmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryAppointmentResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListSurgeryAppointments extends ListRecords
|
||||
{
|
||||
protected static string $resource = SurgeryAppointmentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\SurgeryCenterResource\Pages;
|
||||
use App\Models\SurgeryCenter;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class SurgeryCenterResource extends Resource
|
||||
{
|
||||
protected static ?string $model = SurgeryCenter::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-building-office-2';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.clinic');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_centers.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_centers.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.surgery_centers.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('surgery_centers.fields.name'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpanFull(),
|
||||
|
||||
TextInput::make('phone')
|
||||
->label(__('surgery_centers.fields.phone'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('email')
|
||||
->label(__('surgery_centers.fields.email'))
|
||||
->email()
|
||||
->maxLength(100),
|
||||
|
||||
TextInput::make('contact_person')
|
||||
->label(__('surgery_centers.fields.contact_person'))
|
||||
->maxLength(100)
|
||||
->columnSpanFull(),
|
||||
|
||||
Textarea::make('address')
|
||||
->label(__('surgery_centers.fields.address'))
|
||||
->rows(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Textarea::make('description')
|
||||
->label(__('surgery_centers.fields.description'))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('surgery_centers.fields.name'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('phone')
|
||||
->label(__('surgery_centers.fields.phone'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('email')
|
||||
->label(__('surgery_centers.fields.email'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('contact_person')
|
||||
->label(__('surgery_centers.fields.contact_person'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('address')
|
||||
->label(__('surgery_centers.fields.address'))
|
||||
->limit(40)
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('surgery_centers.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('surgery_centers.fields.name'),
|
||||
__('surgery_centers.fields.phone'),
|
||||
__('surgery_centers.fields.email'),
|
||||
__('surgery_centers.fields.contact_person'),
|
||||
__('surgery_centers.fields.address'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->phone,
|
||||
$record->email,
|
||||
$record->contact_person,
|
||||
$record->address,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'surgery-centers-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = SurgeryCenter::orderBy('name')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('surgery_centers.fields.name'),
|
||||
__('surgery_centers.fields.phone'),
|
||||
__('surgery_centers.fields.email'),
|
||||
__('surgery_centers.fields.contact_person'),
|
||||
__('surgery_centers.fields.address'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->phone,
|
||||
$record->email,
|
||||
$record->contact_person,
|
||||
$record->address,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'surgery-centers-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = SurgeryCenter::orderBy('name')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.surgery-centers-pdf',
|
||||
['records' => $records],
|
||||
'surgery-centers-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListSurgeryCenters::route('/'),
|
||||
'create' => Pages\CreateSurgeryCenter::route('/create'),
|
||||
'edit' => Pages\EditSurgeryCenter::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryCenterResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryCenterResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateSurgeryCenter extends CreateRecord
|
||||
{
|
||||
protected static string $resource = SurgeryCenterResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryCenterResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryCenterResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditSurgeryCenter extends EditRecord
|
||||
{
|
||||
protected static string $resource = SurgeryCenterResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\SurgeryCenterResource\Pages;
|
||||
|
||||
use App\Filament\Resources\SurgeryCenterResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListSurgeryCenters extends ListRecords
|
||||
{
|
||||
protected static string $resource = SurgeryCenterResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TreatmentResource\Pages;
|
||||
use App\Models\Treatment;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class TreatmentResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Treatment::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'treatment_name';
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-clipboard-document-list';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.diseases');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.treatments.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.treatments.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.treatments.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('treatment_type')
|
||||
->label(__('treatments.fields.treatment_type'))
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('treatment_name')
|
||||
->label(__('treatments.fields.treatment_name'))
|
||||
->required()
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('treatment_cost')
|
||||
->label(__('treatments.fields.treatment_cost'))
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
|
||||
Textarea::make('descriptions')
|
||||
->label(__('treatments.fields.descriptions'))
|
||||
->rows(3)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('treatment_name')
|
||||
->label(__('treatments.fields.treatment_name'))
|
||||
->weight(FontWeight::Medium)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('treatment_type')
|
||||
->label(__('treatments.fields.treatment_type'))
|
||||
->badge()
|
||||
->color('primary')
|
||||
->placeholder('-')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('treatment_cost')
|
||||
->label(__('treatments.fields.treatment_cost'))
|
||||
->numeric(thousandsSeparator: ',')
|
||||
->placeholder('-')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('descriptions')
|
||||
->label(__('treatments.fields.descriptions'))
|
||||
->limit(40)
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('treatments.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('treatments.fields.treatment_name'),
|
||||
__('treatments.fields.treatment_type'),
|
||||
__('treatments.fields.treatment_cost'),
|
||||
__('treatments.fields.descriptions'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->treatment_name,
|
||||
$record->treatment_type,
|
||||
$record->treatment_cost,
|
||||
$record->descriptions,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'treatments-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Treatment::orderBy('treatment_type')->orderBy('treatment_name')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('treatments.fields.treatment_name'),
|
||||
__('treatments.fields.treatment_type'),
|
||||
__('treatments.fields.treatment_cost'),
|
||||
__('treatments.fields.descriptions'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->treatment_name,
|
||||
$record->treatment_type,
|
||||
$record->treatment_cost,
|
||||
$record->descriptions,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'treatments-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Treatment::orderBy('treatment_type')->orderBy('treatment_name')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.treatments-pdf',
|
||||
['records' => $records],
|
||||
'treatments-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTreatments::route('/'),
|
||||
'create' => Pages\CreateTreatment::route('/create'),
|
||||
'edit' => Pages\EditTreatment::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TreatmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TreatmentResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTreatment extends CreateRecord
|
||||
{
|
||||
protected static string $resource = TreatmentResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TreatmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TreatmentResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTreatment extends EditRecord
|
||||
{
|
||||
protected static string $resource = TreatmentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TreatmentResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TreatmentResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTreatments extends ListRecords
|
||||
{
|
||||
protected static string $resource = TreatmentResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Models\User;
|
||||
use App\Services\PdfService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return ['name', 'username'];
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-users';
|
||||
}
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.clinic');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('users.fields.name'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('username')
|
||||
->label(__('users.fields.username'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(table: User::class, column: 'username', ignoreRecord: true),
|
||||
|
||||
TextInput::make('password')
|
||||
->label(__('users.fields.password'))
|
||||
->password()
|
||||
->required(fn (string $operation): bool => $operation === 'create')
|
||||
->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
|
||||
->dehydrated(fn (?string $state): bool => filled($state))
|
||||
->maxLength(255),
|
||||
|
||||
Select::make('roles')
|
||||
->label(__('users.fields.roles'))
|
||||
->relationship('roles', 'name')
|
||||
->multiple()
|
||||
->preload(),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('createAnother')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create_another.label'))
|
||||
->action('createAnother')
|
||||
->keyBindings(['mod+shift+s'])
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(__('users.fields.name'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('username')
|
||||
->label(__('users.fields.username'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('roles.name')
|
||||
->label(__('users.fields.roles'))
|
||||
->badge()
|
||||
->separator(','),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('users.fields.created_at'))
|
||||
->sortable(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('users.fields.name'),
|
||||
__('users.fields.username'),
|
||||
__('users.fields.roles'),
|
||||
__('users.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->username,
|
||||
$record->roles->pluck('name')->join(', '),
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'users-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = User::with('roles')->get();
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('users.fields.name'),
|
||||
__('users.fields.username'),
|
||||
__('users.fields.roles'),
|
||||
__('users.fields.created_at'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->name,
|
||||
$record->username,
|
||||
$record->roles->pluck('name')->join(', '),
|
||||
$record->created_at,
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'users-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = User::with('roles')->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.users-pdf',
|
||||
['records' => $records],
|
||||
'users-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.users.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.users.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.users.title');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,406 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\VisitResource\Pages;
|
||||
use App\Models\Doctor;
|
||||
use App\Models\Patient;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\Treatment;
|
||||
use App\Models\Visit;
|
||||
use App\Services\PdfService;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\TimePicker;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Collection;
|
||||
use OpenSpout\Common\Entity\Row;
|
||||
use OpenSpout\Writer\XLSX\Writer;
|
||||
|
||||
class VisitResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Visit::class;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'visit_date';
|
||||
|
||||
public static function getGloballySearchableAttributes(): array
|
||||
{
|
||||
return ['visit_date', 'patientRecord.first_name', 'patientRecord.last_name', 'treatment_description'];
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): \BackedEnum|string|null
|
||||
{
|
||||
return 'heroicon-o-calendar';
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('navigation.groups.patients');
|
||||
}
|
||||
|
||||
public static function getNavigationSort(): ?int
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('navigation.visits.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('navigation.visits.singular');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('navigation.visits.title');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Grid::make()
|
||||
->schema([
|
||||
Section::make(__('visits.sections.visit_info'))
|
||||
->schema([
|
||||
Select::make('patient')
|
||||
->label(__('visits.fields.patient'))
|
||||
->options(fn () => Patient::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->full_name . ($p->icno ? ' - ' . $p->icno : '')]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->columnSpan(2),
|
||||
|
||||
DatePicker::make('visit_date')
|
||||
->label(__('visits.fields.visit_date'))
|
||||
->jalali()
|
||||
->default(now())
|
||||
->required(),
|
||||
|
||||
TimePicker::make('visit_time')
|
||||
->label(__('visits.fields.visit_time'))
|
||||
->nullable()
|
||||
->native(false)
|
||||
->seconds(false)
|
||||
->minutesStep(15),
|
||||
|
||||
Select::make('doctor')
|
||||
->label(__('visits.fields.doctor'))
|
||||
->options(fn () => Doctor::all()->mapWithKeys(
|
||||
fn ($d) => [$d->id => $d->full_name]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable(),
|
||||
|
||||
Select::make('treatment')
|
||||
->label(__('visits.fields.treatment'))
|
||||
->options(fn () => Treatment::all()->mapWithKeys(
|
||||
fn ($t) => [$t->id => ($t->treatment_name ?: $t->treatment_type)]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, callable $set) {
|
||||
if ($state) {
|
||||
$treatment = Treatment::find($state);
|
||||
if ($treatment && $treatment->treatment_cost > 0) {
|
||||
$set('treatment_cost', $treatment->treatment_cost);
|
||||
}
|
||||
}
|
||||
}),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('visits.sections.financial_info'))
|
||||
->schema([
|
||||
TextInput::make('treatment_cost')
|
||||
->label(__('visits.fields.treatment_cost'))
|
||||
->numeric()
|
||||
->nullable()
|
||||
->suffix(__('visits.fields.currency')),
|
||||
|
||||
TextInput::make('paid_amount')
|
||||
->label(__('visits.fields.paid_amount'))
|
||||
->numeric()
|
||||
->nullable()
|
||||
->suffix(__('visits.fields.currency')),
|
||||
|
||||
Select::make('payment_type')
|
||||
->label(__('visits.fields.payment_type'))
|
||||
->options(fn () => PaymentType::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->payment_type]
|
||||
)->toArray())
|
||||
->searchable()
|
||||
->nullable(),
|
||||
])
|
||||
->columns(3)
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make(__('visits.sections.treatment_details'))
|
||||
->schema([
|
||||
Textarea::make('treatment_description')
|
||||
->label(__('visits.fields.treatment_description'))
|
||||
->rows(4)
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->footerActions([
|
||||
fn (string $operation) => Action::make('create')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.create.label'))
|
||||
->submit('create')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelCreate')
|
||||
->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'create'),
|
||||
|
||||
fn (string $operation) => Action::make('save')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.save.label'))
|
||||
->submit('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->visible($operation === 'edit'),
|
||||
|
||||
fn (string $operation) => Action::make('cancelEdit')
|
||||
->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label'))
|
||||
->url(static::getUrl('index'))
|
||||
->color('gray')
|
||||
->visible($operation === 'edit'),
|
||||
]),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->modifyQueryUsing(fn ($query) => $query->with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord']))
|
||||
->deferLoading()
|
||||
->columns([
|
||||
TextColumn::make('visit_date')
|
||||
->label(__('visits.fields.visit_date'))
|
||||
->sortable()
|
||||
->searchable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('patientRecord.first_name')
|
||||
->label(__('visits.fields.patient'))
|
||||
->formatStateUsing(fn ($record) => $record->patientRecord?->full_name ?? '-')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('doctorRecord.first_name')
|
||||
->label(__('visits.fields.doctor'))
|
||||
->formatStateUsing(fn ($record) => $record->doctorRecord?->full_name ?? '-')
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('treatmentRecord.treatment_name')
|
||||
->label(__('visits.fields.treatment'))
|
||||
->formatStateUsing(fn ($record) => $record->treatmentRecord
|
||||
? ($record->treatmentRecord->treatment_name ?: $record->treatmentRecord->treatment_type)
|
||||
: '-')
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('treatment_cost')
|
||||
->label(__('visits.fields.treatment_cost'))
|
||||
->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-')
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('paid_amount')
|
||||
->label(__('visits.fields.paid_amount'))
|
||||
->formatStateUsing(fn ($state) => $state !== null ? number_format((int) $state) : '-')
|
||||
->sortable()
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('remained_amount')
|
||||
->label(__('visits.fields.remained_amount'))
|
||||
->state(fn ($record) => $record->remained_amount)
|
||||
->formatStateUsing(fn ($state) => number_format((int) $state))
|
||||
->color(fn ($record) => $record->remained_amount > 0 ? 'danger' : 'success')
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('paymentTypeRecord.payment_type')
|
||||
->label(__('visits.fields.payment_type'))
|
||||
->placeholder('-'),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('visits.fields.created_at'))
|
||||
->sortable()
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) {
|
||||
return '-';
|
||||
}
|
||||
try {
|
||||
$date = \Carbon\Carbon::parse($state);
|
||||
return Jalalian::fromCarbon($date)->format('Y/m/d - H:i');
|
||||
} catch (\Exception $e) {
|
||||
return $state;
|
||||
}
|
||||
})
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->filters([
|
||||
SelectFilter::make('doctor')
|
||||
->label(__('visits.fields.doctor'))
|
||||
->options(fn () => Doctor::all()->mapWithKeys(
|
||||
fn ($d) => [$d->id => $d->full_name]
|
||||
)->toArray()),
|
||||
|
||||
SelectFilter::make('treatment')
|
||||
->label(__('visits.fields.treatment'))
|
||||
->options(fn () => Treatment::all()->mapWithKeys(
|
||||
fn ($t) => [$t->id => ($t->treatment_name ?: $t->treatment_type)]
|
||||
)->toArray()),
|
||||
|
||||
SelectFilter::make('payment_type')
|
||||
->label(__('visits.fields.payment_type'))
|
||||
->options(fn () => PaymentType::all()->mapWithKeys(
|
||||
fn ($p) => [$p->id => $p->payment_type]
|
||||
)->toArray()),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
BulkAction::make('export_excel_selected')
|
||||
->label(__('table.export_excel_selected'))
|
||||
->icon('heroicon-o-table-cells')
|
||||
->color('success')
|
||||
->action(function (Collection $records) {
|
||||
return self::exportToExcel($records);
|
||||
}),
|
||||
]),
|
||||
|
||||
ActionGroup::make([
|
||||
Action::make('export_excel')
|
||||
->label(__('table.export_excel'))
|
||||
->color('success')
|
||||
->icon('heroicon-o-table-cells')
|
||||
->action(function () {
|
||||
$records = Visit::with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
return self::exportToExcel($records);
|
||||
}),
|
||||
|
||||
Action::make('export_pdf')
|
||||
->label(__('table.export_pdf'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-document-arrow-down')
|
||||
->action(function () {
|
||||
$records = Visit::with(['patientRecord', 'doctorRecord', 'treatmentRecord', 'paymentTypeRecord'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return PdfService::download(
|
||||
'exports.visits-pdf',
|
||||
['records' => $records],
|
||||
'visits-' . now()->format('Y-m-d') . '.pdf',
|
||||
);
|
||||
}),
|
||||
])
|
||||
->label(__('table.export'))
|
||||
->color('orange')
|
||||
->button(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function exportToExcel(Collection $records): mixed
|
||||
{
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'export_') . '.xlsx';
|
||||
$writer = new Writer();
|
||||
$writer->openToFile($tempPath);
|
||||
|
||||
$writer->addRow(Row::fromValues([
|
||||
'ID',
|
||||
__('visits.fields.visit_date'),
|
||||
__('visits.fields.patient'),
|
||||
__('visits.fields.doctor'),
|
||||
__('visits.fields.treatment'),
|
||||
__('visits.fields.treatment_cost'),
|
||||
__('visits.fields.paid_amount'),
|
||||
__('visits.fields.remained_amount'),
|
||||
__('visits.fields.payment_type'),
|
||||
__('visits.fields.treatment_description'),
|
||||
]));
|
||||
|
||||
foreach ($records as $record) {
|
||||
$writer->addRow(Row::fromValues([
|
||||
$record->id,
|
||||
$record->visit_date ?? '',
|
||||
$record->patientRecord?->full_name ?? '',
|
||||
$record->doctorRecord?->full_name ?? '',
|
||||
$record->treatmentRecord
|
||||
? ($record->treatmentRecord->treatment_name ?: $record->treatmentRecord->treatment_type)
|
||||
: '',
|
||||
$record->treatment_cost ?? 0,
|
||||
$record->paid_amount ?? 0,
|
||||
$record->remained_amount,
|
||||
$record->paymentTypeRecord?->payment_type ?? '',
|
||||
$record->treatment_description ?? '',
|
||||
]));
|
||||
}
|
||||
|
||||
$writer->close();
|
||||
|
||||
return response()->streamDownload(function () use ($tempPath) {
|
||||
readfile($tempPath);
|
||||
@unlink($tempPath);
|
||||
}, 'visits-' . now()->format('Y-m-d') . '.xlsx', [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListVisits::route('/'),
|
||||
'create' => Pages\CreateVisit::route('/create'),
|
||||
'edit' => Pages\EditVisit::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\VisitResource\Pages;
|
||||
|
||||
use App\Filament\Resources\VisitResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class CreateVisit extends CreateRecord
|
||||
{
|
||||
protected static string $resource = VisitResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return static::getResource()::getUrl('index');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\VisitResource\Pages;
|
||||
|
||||
use App\Filament\Resources\VisitResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class EditVisit extends EditRecord
|
||||
{
|
||||
protected static string $resource = VisitResource::class;
|
||||
|
||||
protected Width|string|null $maxContentWidth = Width::SevenExtraLarge;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return static::getResource()::getUrl('index');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\VisitResource\Pages;
|
||||
|
||||
use App\Filament\Resources\VisitResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListVisits extends ListRecords
|
||||
{
|
||||
protected static string $resource = VisitResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('heroicon-o-plus'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Patient;
|
||||
use App\Models\Prescription;
|
||||
use Illuminate\Http\Request;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class PrescriptionPrintController extends Controller
|
||||
{
|
||||
public function print(Request $request, Prescription $prescription)
|
||||
{
|
||||
$prescription->load('patientRecord');
|
||||
$type = $request->query('type', 'prescription');
|
||||
|
||||
return view('prints.prescription', [
|
||||
'prescription' => $prescription,
|
||||
'type' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
public function printAdmission(Prescription $prescription)
|
||||
{
|
||||
$prescription->load('patientRecord');
|
||||
|
||||
return view('prints.prescription-admission', [
|
||||
'prescription' => $prescription,
|
||||
]);
|
||||
}
|
||||
|
||||
public function printPatientLab(Patient $patient)
|
||||
{
|
||||
return view('prints.patient-lab', [
|
||||
'patient' => $patient,
|
||||
'issueDate' => Jalalian::now()->format('Y/m/d'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function printSurgeryAdmission(Patient $patient)
|
||||
{
|
||||
$appointment = $patient->surgeryAppointment?->load('surgeryCenter');
|
||||
|
||||
$surgeryDate = '';
|
||||
$centerName = '';
|
||||
|
||||
if ($appointment) {
|
||||
try {
|
||||
$surgeryDate = Jalalian::fromCarbon($appointment->surgery_date)->format('Y/m/d');
|
||||
} catch (\Throwable) {
|
||||
$surgeryDate = $appointment->surgery_date?->format('Y-m-d') ?? '';
|
||||
}
|
||||
$centerName = $appointment->surgeryCenter?->name ?? '';
|
||||
}
|
||||
|
||||
return view('prints.surgery-admission', [
|
||||
'patient' => $patient,
|
||||
'surgeryDate' => $surgeryDate,
|
||||
'centerName' => $centerName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\OsurgInitial;
|
||||
use App\Models\SyncLog;
|
||||
use App\Services\SyncService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class SyncController extends Controller
|
||||
{
|
||||
protected function verifyToken(Request $request): bool
|
||||
{
|
||||
$token = OsurgInitial::val('sync_token', config('sync.token'));
|
||||
return $token !== '' && $request->header('X-Sync-Token') === $token;
|
||||
}
|
||||
|
||||
public function export(Request $request): JsonResponse
|
||||
{
|
||||
if (! $this->verifyToken($request)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$since = (int) $request->query('since', 0);
|
||||
|
||||
return response()->json([
|
||||
'last_sync_id' => SyncLog::lastSyncId(),
|
||||
'changes' => SyncLog::changesSince($since),
|
||||
]);
|
||||
}
|
||||
|
||||
public function apply(Request $request): JsonResponse
|
||||
{
|
||||
if (! $this->verifyToken($request)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
$service = SyncService::fromConfig();
|
||||
$report = $service->applyChanges($request->input('changes', []));
|
||||
|
||||
return response()->json(['report' => $report]);
|
||||
}
|
||||
|
||||
public function downloadFile(Request $request): mixed
|
||||
{
|
||||
if (! $this->verifyToken($request)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$path = $request->query('path');
|
||||
if (! $path || ! Storage::disk('public')->exists($path)) {
|
||||
return response()->json(['error' => 'File not found'], 404);
|
||||
}
|
||||
|
||||
return Storage::disk('public')->download($path);
|
||||
}
|
||||
|
||||
public function receiveFile(Request $request): JsonResponse
|
||||
{
|
||||
if (! $this->verifyToken($request)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
$path = $request->input('path');
|
||||
$file = $request->file('file');
|
||||
|
||||
if (! $path || ! $file) {
|
||||
return response()->json(['error' => 'Missing path or file'], 400);
|
||||
}
|
||||
|
||||
$path = ltrim(str_replace(['..', '\\'], ['', '/'], $path), '/');
|
||||
|
||||
Storage::disk('public')->put($path, file_get_contents($file->getRealPath()));
|
||||
|
||||
return response()->json(['success' => true, 'path' => $path]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SetLocale
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$locale = session('locale', config('app.locale'));
|
||||
|
||||
if (in_array($locale, config('app.available_locales', ['fa', 'en']))) {
|
||||
app()->setLocale($locale);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Services\SyncService;
|
||||
use Filament\Notifications\Notification;
|
||||
use Livewire\Component;
|
||||
|
||||
class SettingsDropdown extends Component
|
||||
{
|
||||
public bool $syncSuccess = false;
|
||||
public bool $syncFailed = false;
|
||||
public array $reportLines = [];
|
||||
|
||||
public function runSync(): void
|
||||
{
|
||||
$service = SyncService::fromConfig();
|
||||
|
||||
if (! $service->isConfigured()) {
|
||||
$this->syncFailed = true;
|
||||
$this->reportLines = [__('navigation.sync.error_not_configured')];
|
||||
$this->dispatch('sync-finished', success: false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $service->sync();
|
||||
|
||||
$this->reportLines = $result['report'] ?? [];
|
||||
|
||||
if ($result['success']) {
|
||||
$this->syncSuccess = true;
|
||||
$this->dispatch('sync-finished', success: true);
|
||||
} else {
|
||||
$this->syncFailed = true;
|
||||
$this->dispatch('sync-finished', success: false);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings-dropdown');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Doctor extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'speciality',
|
||||
'license_id',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return $this->first_name . ' ' . $this->last_name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Medication extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'dosage_form',
|
||||
'strength',
|
||||
'quantity',
|
||||
'timing',
|
||||
'notes',
|
||||
'is_default',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_default' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'quantity' => 'integer',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OsurgInitial extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'init_parameter',
|
||||
'init_value',
|
||||
'attachment',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public static function val(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return cache()->remember(
|
||||
"osurg_initial.{$key}",
|
||||
60,
|
||||
fn () => static::where('init_parameter', $key)->value('init_value') ?? $default
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use App\Traits\HasUserStamps;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
class Patient extends Model
|
||||
{
|
||||
use HasUserStamps;
|
||||
protected $fillable = [
|
||||
'icno',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'father_name',
|
||||
'gender',
|
||||
'birth_date',
|
||||
'age',
|
||||
'marital_status',
|
||||
'education',
|
||||
'job',
|
||||
'hand_phone',
|
||||
'home_phone',
|
||||
'work_phone',
|
||||
'other_phone',
|
||||
'home_address',
|
||||
'work_address',
|
||||
'refered_by',
|
||||
'referal_reason',
|
||||
'doc_id',
|
||||
'insurance',
|
||||
'insurance_no',
|
||||
'blood_pressure',
|
||||
'blood_sugar',
|
||||
'current_illness_1',
|
||||
'current_illness_2',
|
||||
'is_undercare',
|
||||
'undercare_reason',
|
||||
'is_usingdrug',
|
||||
'underdrug_reason',
|
||||
'has_alergyto',
|
||||
'alergy_reason',
|
||||
'description',
|
||||
'surgery_before',
|
||||
'sergery_after',
|
||||
'photos_before',
|
||||
'photos_after',
|
||||
'videos',
|
||||
'audio_files',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'blood_pressure' => 'integer',
|
||||
'blood_sugar' => 'integer',
|
||||
'age' => 'integer',
|
||||
'current_illness_1' => 'array',
|
||||
'current_illness_2' => 'array',
|
||||
'has_alergyto' => 'array',
|
||||
'photos_before' => 'array',
|
||||
'photos_after' => 'array',
|
||||
'videos' => 'array',
|
||||
'audio_files' => 'array',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return trim($this->first_name . ' ' . $this->last_name);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function updator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
|
||||
public function surgeryAppointment(): HasOne
|
||||
{
|
||||
return $this->hasOne(SurgeryAppointment::class);
|
||||
}
|
||||
|
||||
public function prescriptions(): HasMany
|
||||
{
|
||||
return $this->hasMany(Prescription::class, 'patient');
|
||||
}
|
||||
|
||||
public function visits(): HasMany
|
||||
{
|
||||
return $this->hasMany(Visit::class, 'patient');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PatientIllness extends Model
|
||||
{
|
||||
protected $table = 'patient_illness';
|
||||
|
||||
protected $fillable = [
|
||||
'illness',
|
||||
'row_no',
|
||||
'priority',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'row_no' => 'integer',
|
||||
'priority' => 'integer',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentType extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'payment_type',
|
||||
'account_no',
|
||||
'pos_no',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use App\Traits\HasUserStamps;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Prescription extends Model
|
||||
{
|
||||
use HasUserStamps;
|
||||
|
||||
protected $fillable = [
|
||||
'patient',
|
||||
'issue_date',
|
||||
'issue_datetime',
|
||||
'doctor',
|
||||
'medication_ids',
|
||||
'content',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'issue_datetime' => 'datetime',
|
||||
'medication_ids' => 'array',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function patientRecord(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Patient::class, 'patient');
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function updator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use App\Traits\HasUserStamps;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SurgeryAppointment extends Model
|
||||
{
|
||||
use HasUserStamps;
|
||||
|
||||
protected $fillable = [
|
||||
'patient_id',
|
||||
'surgery_date',
|
||||
'surgery_center_id',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'surgery_date' => 'datetime',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function patient(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Patient::class);
|
||||
}
|
||||
|
||||
public function surgeryCenter(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SurgeryCenter::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function updator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SurgeryCenter extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'address',
|
||||
'phone',
|
||||
'email',
|
||||
'contact_person',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SyncLog extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'sync_log';
|
||||
|
||||
protected $fillable = [
|
||||
'datetime',
|
||||
'ip',
|
||||
'user',
|
||||
'table_name',
|
||||
'action',
|
||||
'record_id',
|
||||
'changed_data',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'datetime' => 'datetime',
|
||||
'changed_data' => 'array',
|
||||
];
|
||||
|
||||
public static function lastSyncId(): int
|
||||
{
|
||||
return (int) static::where('action', 'synced')->max('id') ?: 0;
|
||||
}
|
||||
|
||||
public static function changesSince(int $since): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return static::where('id', '>', $since)
|
||||
->whereIn('action', ['created', 'updated', 'deleted'])
|
||||
->orderBy('id')
|
||||
->get();
|
||||
}
|
||||
|
||||
public static function markSynced(string $ip): static
|
||||
{
|
||||
return static::create([
|
||||
'datetime' => now(),
|
||||
'ip' => $ip,
|
||||
'user' => auth()->user()?->name ?? 'system',
|
||||
'table_name' => '',
|
||||
'action' => 'synced',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Treatment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'treatment_type',
|
||||
'treatment_name',
|
||||
'descriptions',
|
||||
'treatment_cost',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'treatment_cost' => 'integer',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPanelShield;
|
||||
use Database\Factories\UserFactory;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class User extends Authenticatable implements HasAvatar
|
||||
{
|
||||
use HasFactory, Notifiable, HasRoles, HasPanelShield;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getFilamentAvatarUrl(): ?string
|
||||
{
|
||||
$name = $this->name ?? $this->username ?? '?';
|
||||
$initials = $this->getAvatarInitials($name);
|
||||
$color = $this->getAvatarColor($name);
|
||||
|
||||
$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">'
|
||||
. '<circle cx="50" cy="50" r="50" fill="' . $color . '"/>'
|
||||
. '<text x="50" y="50" font-size="38" text-anchor="middle" dominant-baseline="central" fill="white" font-family="Vazirmatn,Tahoma,sans-serif">' . $initials . '</text>'
|
||||
. '</svg>';
|
||||
|
||||
return 'data:image/svg+xml;base64,' . base64_encode($svg);
|
||||
}
|
||||
|
||||
private function getAvatarInitials(string $name): string
|
||||
{
|
||||
$words = preg_split('/\s+/', trim($name));
|
||||
if (count($words) >= 2) {
|
||||
return mb_substr($words[0], 0, 1) . mb_substr($words[1], 0, 1);
|
||||
}
|
||||
|
||||
return mb_substr($name, 0, 2);
|
||||
}
|
||||
|
||||
private function getAvatarColor(string $name): string
|
||||
{
|
||||
$colors = ['#4F46E5', '#7C3AED', '#2563EB', '#0891B2', '#059669', '#D97706', '#DC2626', '#0F766E'];
|
||||
$index = abs(crc32($name) % count($colors));
|
||||
|
||||
return $colors[$index];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\JalaliDatetime;
|
||||
use App\Traits\HasUserStamps;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Visit extends Model
|
||||
{
|
||||
use HasUserStamps;
|
||||
|
||||
protected $fillable = [
|
||||
'patient',
|
||||
'visit_date',
|
||||
'visit_time',
|
||||
'treatment',
|
||||
'treatment_cost',
|
||||
'payment_type',
|
||||
'paid_amount',
|
||||
'pos_ul',
|
||||
'pos_ll',
|
||||
'pos_ur',
|
||||
'pos_lr',
|
||||
'treatment_description',
|
||||
'doctor',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'treatment_cost' => 'integer',
|
||||
'paid_amount' => 'integer',
|
||||
'pos_ul' => 'integer',
|
||||
'pos_ll' => 'integer',
|
||||
'pos_ur' => 'integer',
|
||||
'pos_lr' => 'integer',
|
||||
'created_at' => JalaliDatetime::class,
|
||||
'updated_at' => JalaliDatetime::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getRemainedAmountAttribute(): int
|
||||
{
|
||||
return (int) ($this->treatment_cost ?? 0) - (int) ($this->paid_amount ?? 0);
|
||||
}
|
||||
|
||||
public function patientRecord(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Patient::class, 'patient');
|
||||
}
|
||||
|
||||
public function doctorRecord(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Doctor::class, 'doctor');
|
||||
}
|
||||
|
||||
public function treatmentRecord(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Treatment::class, 'treatment');
|
||||
}
|
||||
|
||||
public function paymentTypeRecord(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PaymentType::class, 'payment_type');
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function updator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\SyncLog;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SyncObserver
|
||||
{
|
||||
public function created(Model $model): void
|
||||
{
|
||||
if (app()->bound('sync.applying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncLog::create([
|
||||
'datetime' => now(),
|
||||
'ip' => request()->ip() ?? '127.0.0.1',
|
||||
'user' => auth()->user()?->name ?? 'system',
|
||||
'table_name' => $model->getTable(),
|
||||
'action' => 'created',
|
||||
'record_id' => $model->getKey(),
|
||||
'changed_data' => $model->getAttributes(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updated(Model $model): void
|
||||
{
|
||||
if (app()->bound('sync.applying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dirty = $model->getDirty();
|
||||
if (empty($dirty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$changedData = [];
|
||||
foreach ($dirty as $field => $newValue) {
|
||||
$changedData[$field] = $model->getAttribute($field);
|
||||
}
|
||||
|
||||
SyncLog::create([
|
||||
'datetime' => now(),
|
||||
'ip' => request()->ip() ?? '127.0.0.1',
|
||||
'user' => auth()->user()?->name ?? 'system',
|
||||
'table_name' => $model->getTable(),
|
||||
'action' => 'updated',
|
||||
'record_id' => $model->getKey(),
|
||||
'changed_data' => $changedData,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleted(Model $model): void
|
||||
{
|
||||
if (app()->bound('sync.applying')) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncLog::create([
|
||||
'datetime' => now(),
|
||||
'ip' => request()->ip() ?? '127.0.0.1',
|
||||
'user' => auth()->user()?->name ?? 'system',
|
||||
'table_name' => $model->getTable(),
|
||||
'action' => 'deleted',
|
||||
'record_id' => $model->getKey(),
|
||||
'changed_data' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Doctor;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class DoctorPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Doctor');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('View:Doctor');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Doctor');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('Update:Doctor');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('Delete:Doctor');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('Restore:Doctor');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Doctor');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Doctor');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Doctor');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Doctor $doctor): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Doctor');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Doctor');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Medication;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class MedicationPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Medication');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('View:Medication');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Medication');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('Update:Medication');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('Delete:Medication');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('Restore:Medication');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Medication');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Medication');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Medication');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Medication $medication): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Medication');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Medication');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\OsurgInitial;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class OsurgInitialPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:OsurgInitial');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('View:OsurgInitial');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:OsurgInitial');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('Update:OsurgInitial');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('Delete:OsurgInitial');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('Restore:OsurgInitial');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:OsurgInitial');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:OsurgInitial');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:OsurgInitial');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, OsurgInitial $osurgInitial): bool
|
||||
{
|
||||
return $authUser->can('Replicate:OsurgInitial');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:OsurgInitial');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\PatientIllness;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PatientIllnessPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:PatientIllness');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('View:PatientIllness');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:PatientIllness');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('Update:PatientIllness');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('Delete:PatientIllness');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('Restore:PatientIllness');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:PatientIllness');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:PatientIllness');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:PatientIllness');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, PatientIllness $patientIllness): bool
|
||||
{
|
||||
return $authUser->can('Replicate:PatientIllness');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:PatientIllness');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Patient;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PatientPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Patient');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('View:Patient');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Patient');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('Update:Patient');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('Delete:Patient');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('Restore:Patient');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Patient');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Patient');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Patient');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Patient $patient): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Patient');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Patient');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PaymentTypePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:PaymentType');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('View:PaymentType');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:PaymentType');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('Update:PaymentType');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('Delete:PaymentType');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('Restore:PaymentType');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:PaymentType');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:PaymentType');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:PaymentType');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, PaymentType $paymentType): bool
|
||||
{
|
||||
return $authUser->can('Replicate:PaymentType');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:PaymentType');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Prescription;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class PrescriptionPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Prescription');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('View:Prescription');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Prescription');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('Update:Prescription');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('Delete:Prescription');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('Restore:Prescription');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Prescription');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Prescription');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Prescription');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Prescription $prescription): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Prescription');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Prescription');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class RolePolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Role');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('View:Role');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Role');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('Update:Role');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('Delete:Role');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('Restore:Role');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Role');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Role');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Role');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Role $role): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Role');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Role');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\SurgeryAppointment;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class SurgeryAppointmentPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('View:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('Update:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('Delete:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('Restore:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, SurgeryAppointment $surgeryAppointment): bool
|
||||
{
|
||||
return $authUser->can('Replicate:SurgeryAppointment');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:SurgeryAppointment');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\SurgeryCenter;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class SurgeryCenterPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('View:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('Update:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('Delete:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('Restore:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, SurgeryCenter $surgeryCenter): bool
|
||||
{
|
||||
return $authUser->can('Replicate:SurgeryCenter');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:SurgeryCenter');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\SyncLog;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class SyncLogPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:SyncLog');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('View:SyncLog');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:SyncLog');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('Update:SyncLog');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('Delete:SyncLog');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('Restore:SyncLog');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:SyncLog');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:SyncLog');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:SyncLog');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, SyncLog $syncLog): bool
|
||||
{
|
||||
return $authUser->can('Replicate:SyncLog');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:SyncLog');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Treatment;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TreatmentPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Treatment');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('View:Treatment');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Treatment');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('Update:Treatment');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('Delete:Treatment');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('Restore:Treatment');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Treatment');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Treatment');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Treatment');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Treatment $treatment): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Treatment');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Treatment');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:User');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('View:User');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:User');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Update:User');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Delete:User');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Restore:User');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:User');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:User');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:User');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Replicate:User');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:User');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as AuthUser;
|
||||
use App\Models\Visit;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class VisitPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ViewAny:Visit');
|
||||
}
|
||||
|
||||
public function view(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('View:Visit');
|
||||
}
|
||||
|
||||
public function create(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Create:Visit');
|
||||
}
|
||||
|
||||
public function update(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('Update:Visit');
|
||||
}
|
||||
|
||||
public function delete(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('Delete:Visit');
|
||||
}
|
||||
|
||||
public function restore(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('Restore:Visit');
|
||||
}
|
||||
|
||||
public function forceDelete(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('ForceDelete:Visit');
|
||||
}
|
||||
|
||||
public function forceDeleteAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('ForceDeleteAny:Visit');
|
||||
}
|
||||
|
||||
public function restoreAny(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('RestoreAny:Visit');
|
||||
}
|
||||
|
||||
public function replicate(AuthUser $authUser, Visit $visit): bool
|
||||
{
|
||||
return $authUser->can('Replicate:Visit');
|
||||
}
|
||||
|
||||
public function reorder(AuthUser $authUser): bool
|
||||
{
|
||||
return $authUser->can('Reorder:Visit');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue