feat: Persian panel/API fixes and structural-model tweaks

- lang/fa: add validation/auth/passwords/pagination translations so admin
  and API validation errors are in Persian
- templates: soft-delete instead of cascade-deleting their generations;
  keep resolving trashed template on generations (labels/downloads)
- templates: add TemplateInfolist with a real thumbnail ImageEntry
- generations: auto-name template-based generations and use that name
  for the download file and as a searchable column
- admin: show all dates as Jalali via ->jalaliDate()/->jalaliDateTime()
  Filament macros (App\Support\Jalali)
- wallets: harden the deposit/withdraw action to surface the exact error
  instead of a 500
- profile: normalize Iranian mobile numbers (App\Support\Mobile),
  add social_links, coerce notification-preference booleans, require the
  current password to delete an account

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013P66ea1dC5CVM9t29496t5
This commit is contained in:
2026-09-07 01:00:09 +03:30
parent 46ae711a20
commit 936f789ac8
38 changed files with 756 additions and 39 deletions

View File

@@ -44,7 +44,7 @@ class TransactionsRelationManager extends RelationManager
->placeholder('سیستم'),
TextColumn::make('created_at')
->label('تاریخ')
->dateTime()
->jalaliDateTime()
->sortable(),
])
->defaultSort('created_at', 'desc')

View File

@@ -15,7 +15,7 @@ use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use RuntimeException;
use Throwable;
class WalletsTable
{
@@ -45,7 +45,7 @@ class WalletsTable
->boolean(),
TextColumn::make('updated_at')
->label('آخرین بروزرسانی')
->dateTime()
->jalaliDateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
@@ -84,21 +84,27 @@ class WalletsTable
->rows(2),
])
->action(function (Wallet $record, array $data, WalletService $walletService): void {
$amount = (int) $data['amount'];
$description = filled($data['description'] ?? null) ? $data['description'] : null;
try {
$transaction = $type === 'credit'
? $walletService->credit($record, (int) $data['amount'], $data['description'] ?? null, auth()->user())
: $walletService->debit($record, (int) $data['amount'], $data['description'] ?? null, auth()->user());
? $walletService->credit($record, $amount, $description, auth()->user())
: $walletService->debit($record, $amount, $description, auth()->user());
Notification::make()
->title($type === 'credit' ? 'واریز انجام شد' : 'برداشت انجام شد')
->body('موجودی جدید: '.number_format($transaction->balance_after).' تومان')
->success()
->send();
} catch (RuntimeException $exception) {
} catch (Throwable $exception) {
report($exception);
Notification::make()
->title('خطا')
->body($exception->getMessage())
->title($type === 'credit' ? 'واریز ناموفق بود' : 'برداشت ناموفق بود')
->body($exception->getMessage() ?: 'خطای نامشخص هنگام ثبت تراکنش.')
->danger()
->persistent()
->send();
}
});