1719 lines
93 KiB
PHP
1719 lines
93 KiB
PHP
<div class="mt-12">
|
|
|
|
<!-- Action buttons -->
|
|
@unless ($this->isAccountsOnlyScope)
|
|
<div class="mb-6">
|
|
<livewire:profiles.create>
|
|
</div>
|
|
@endunless
|
|
|
|
<!-- Search box -->
|
|
<div class="mb-4 flex items-center">
|
|
<div class="relative w-2/3">
|
|
<input class="w-full rounded-md border border-theme-primary px-3 py-1 pr-10 text-theme-primary shadow-sm focus:border-theme-accent focus:outline-none focus:ring-1 focus:ring-theme-accent sm:text-sm"
|
|
placeholder="{{ __('Search keywords') . '...' }}" type="text"
|
|
wire:keydown.enter="handleSearchEnter" wire:model="search">
|
|
|
|
@if ($search)
|
|
<button class="absolute inset-y-0 right-0 flex items-center pr-3 text-theme-secondary hover:text-theme-primary focus:outline-none"
|
|
wire:click.prevent="resetSearch">
|
|
<x-icon mini name="backspace" solid />
|
|
</button>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Search Button -->
|
|
<x-jetstream.secondary-button class="ml-3 w-32 justify-center" wire:click.prevent="searchProfiles">
|
|
{{ __('Search') }}
|
|
</x-jetstream.secondary-button>
|
|
</div>
|
|
|
|
<!-- Filter dropdowns -->
|
|
<div class="mb-4 flex flex-wrap items-center gap-3">
|
|
<!-- Type Filter Dropdown -->
|
|
<div>
|
|
<x-select :clearable="true" :searchable="false" class="!w-40" style="width: 10rem !important; min-width: 10rem !important;"
|
|
placeholder="{{ __('Type') }}" wire:model.live="typeFilter">
|
|
@foreach ($this->typeOptions as $type)
|
|
<x-select.option label="{{ $type['name'] }}" value="{{ $type['id'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
</div>
|
|
|
|
<!-- Active Status Filter Dropdown -->
|
|
<div>
|
|
<x-select :clearable="true" :searchable="false" class="!w-40" style="width: 10rem !important; min-width: 10rem !important;"
|
|
placeholder="{{ __('Status') }}" wire:model.live="activeFilter">
|
|
@foreach ($this->activeOptions as $status)
|
|
<x-select.option label="{{ $status['name'] }}" value="{{ $status['id'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
</div>
|
|
|
|
<!-- Email Verified Filter Dropdown -->
|
|
<div>
|
|
<x-select :clearable="true" :searchable="false" class="!w-40" style="width: 10rem !important; min-width: 10rem !important;"
|
|
placeholder="{{ __('Email') }}" wire:model.live="emailVerifiedFilter">
|
|
@foreach ($this->emailVerifiedOptions as $status)
|
|
<x-select.option label="{{ $status['name'] }}" value="{{ $status['id'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="bg-white shadow-sm rounded-lg" x-data="{
|
|
initScrollSync() {
|
|
if (this.$refs.topScroll && this.$refs.bottomScroll && this.$refs.table && this.$refs.topScrollContent) {
|
|
const topScroll = this.$refs.topScroll;
|
|
const bottomScroll = this.$refs.bottomScroll;
|
|
const table = this.$refs.table;
|
|
|
|
// Set the width of the dummy div to match table width
|
|
this.$refs.topScrollContent.style.width = table.scrollWidth + 'px';
|
|
}
|
|
}
|
|
}" x-init="setTimeout(() => initScrollSync(), 100)">
|
|
|
|
<!-- Top scrollbar -->
|
|
<div class="overflow-x-auto border-b border-gray-200" x-ref="topScroll" @scroll="$refs.bottomScroll.scrollLeft = $event.target.scrollLeft">
|
|
<div x-ref="topScrollContent" style="height: 20px;"></div>
|
|
</div>
|
|
|
|
<!-- Table with bottom scrollbar -->
|
|
<div class="overflow-x-auto" x-ref="bottomScroll" @scroll="$refs.topScroll.scrollLeft = $event.target.scrollLeft">
|
|
<table class="min-w-full divide-y divide-gray-200" x-ref="table">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
{{-- Sortable ID column --}}
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('id')">
|
|
<div class="flex items-center">
|
|
{{ __('Id') }}
|
|
@if ($sortField === 'id')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
{{-- Sortable Language column --}}
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('locale')">
|
|
<div class="flex items-center">
|
|
|
|
</div>
|
|
</th>
|
|
|
|
{{-- Sortable User Name column --}}
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('name')">
|
|
<div class="flex items-center">
|
|
{{ __('Name') }}
|
|
@if ($sortField === 'name')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
{{-- Sortable Type column --}}
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('type')">
|
|
<div class="flex items-center">
|
|
{{ __('Type') }}
|
|
@if ($sortField === 'type')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
{{-- Sortable Comment column --}}
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('comment')">
|
|
<div class="flex items-center">
|
|
{{ __('Comment') }}
|
|
@if ($sortField === 'comment')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('inactive')">
|
|
<div class="flex items-center">
|
|
{{ __('Active') }}
|
|
@if ($sortField === 'inactive')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('email_verif')">
|
|
<div class="flex items-center">
|
|
{{ __('Conf.') }}
|
|
@if ($sortField === 'email_verif')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('last_login_at')">
|
|
<div class="flex items-center">
|
|
{{ __('Last login') }}
|
|
@if ($sortField === 'last_login_at')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
|
wire:click="sortBy('updated_at')">
|
|
<div class="flex items-center">
|
|
{{ __('Created') }}
|
|
@if ($sortField === 'created_at')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="ml-1 h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
|
|
{{-- Non-sortable Action columns --}}
|
|
<th class="px-3 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider w-32">{{ __('Actions') }}</th>
|
|
|
|
</tr>
|
|
</thead>
|
|
|
|
<!-- Table body -->
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@forelse ($profiles as $profile)
|
|
<!-- Profiles --->
|
|
<tr>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $profile->id }}
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap">
|
|
@if ($profile->profile_photo_path)
|
|
<div class="flex justify-center">
|
|
<div class="relative cursor-pointer h-7 w-7 flex-shrink-0"
|
|
onclick="window.location='{{ route('profile.show_by_type_and_id', ['type' => strtolower(__(strtolower($profile->type))), 'id' => $profile->id]) }}'">
|
|
<img alt="profile"
|
|
class="h-full w-full rounded-full object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
|
|
src="{{ $profile->profile_photo_path ? Storage::url($profile->profile_photo_path) : Storage::url(timebank_config('profiles.' . strtolower(class_basename($profile->model)) . '.profile_photo_path_default')) }}" />
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-3 py-4">
|
|
<div class="text-sm font-medium text-gray-900">
|
|
{{ $profile->name }}
|
|
</div>
|
|
<div class="text-sm text-gray-500">
|
|
{{ $profile->full_name }}
|
|
</div>
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{{ $profile->type }}
|
|
</td>
|
|
<td class="px-3 py-4 text-sm text-gray-500">
|
|
<div class="line-clamp-3">
|
|
{{ $profile->comment }}
|
|
</div>
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
@if ($profile->deleted === __('yes'))
|
|
<div class="text-red-500">
|
|
{{ __('removed') }}
|
|
</div>
|
|
@elseif ($profile->deleted === __('planned'))
|
|
<div class="text-red-500">
|
|
{{ __('removing') }}
|
|
</div>
|
|
@elseif ($profile->inactive == __('no'))
|
|
{{ __('yes') }}
|
|
@elseif ($profile->inactive == __('yes'))
|
|
<div class="text-red-500">
|
|
{{ __('no') }}
|
|
</div>
|
|
@else
|
|
<div class="text-red-500">
|
|
{{ __('pausing') }}
|
|
</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
@if ($profile->email_suppressed)
|
|
<div class="text-red-500 font-medium">
|
|
{{ __('Blocked') }}
|
|
</div>
|
|
@elseif ($profile->email_verif == __('yes'))
|
|
{{ __('yes') }}
|
|
@else
|
|
<div class="text-red-500">
|
|
{{ $profile->email_verif }}
|
|
</div>
|
|
@endif
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
@if ($profile->last_login_at)
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($profile->last_login_at), config('app.timezone'))->diffForHumans() }}
|
|
@endif
|
|
</td>
|
|
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
@if ($profile->created_at)
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($profile->created_at), config('app.timezone'))->diffForHumans() }}
|
|
@endif
|
|
</td>
|
|
|
|
<!-- Row buttons -->
|
|
<td class="px-3 py-4 whitespace-nowrap text-center text-sm font-medium">
|
|
<div class="flex items-center justify-center space-x-1">
|
|
<x-jetstream.secondary-button
|
|
:disabled="$profile->type === 'Admin'"
|
|
title="{{ __('Accounts') }}"
|
|
wire:click="openEditAccountsModal({{ $profile->id }}, '{{ addslashes(get_class($profile)) }}')">
|
|
<x-icon class="h-5 w-5" name="wallet" />
|
|
</x-jetstream.secondary-button>
|
|
|
|
@unless ($this->isAccountsOnlyScope)
|
|
<x-jetstream.secondary-button
|
|
:disabled="$profile->deleted === __('yes')"
|
|
title="{{ $profile->deleted === __('yes') ? __('Profile is deleted') : __('Profile') }}"
|
|
wire:click="openEditProfileModal({{ $profile->id }}, '{{ addslashes(get_class($profile)) }}')">
|
|
<x-icon class="h-5 w-5" name="user" />
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.secondary-button
|
|
:disabled="$profile->deleted === __('yes')"
|
|
title="{{ $profile->deleted === __('yes') ? __('Profile is deleted') : __('Profile links') }}"
|
|
wire:click="openAttachProfilesModal({{ $profile->id }}, '{{ addslashes(get_class($profile)) }}')">
|
|
<x-icon class="h-5 w-5" name="link" />
|
|
</x-jetstream.secondary-button>
|
|
|
|
@if ($profile->deleted === __('yes'))
|
|
{{-- Show restore button for deleted profiles --}}
|
|
<x-jetstream.danger-button
|
|
:disabled="!$profile->is_restorable"
|
|
title="{{ $profile->is_restorable ? __('Restore profile') : __('Cannot restore: The grace period has passed.') }}"
|
|
wire:click="openRestoreProfileModal({{ $profile->id }}, '{{ addslashes(get_class($profile)) }}')">
|
|
<x-icon class="h-5 w-5" name="arrow-path" />
|
|
</x-jetstream.danger-button>
|
|
@else
|
|
{{-- Show delete button for active profiles --}}
|
|
<x-jetstream.danger-button
|
|
title="{{ __('Delete profile') }}"
|
|
wire:click="openDeleteProfileModal({{ $profile->id }}, '{{ addslashes(get_class($profile)) }}')">
|
|
<x-icon class="h-5 w-5" name="trash" />
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
@endunless
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="10" class="px-6 py-12 text-center text-gray-500">
|
|
{{ __('No results found') }}
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
@if($profiles->hasPages())
|
|
<div class="bg-white shadow-sm rounded-lg mt-4">
|
|
<div class="py-3 border-t border-gray-200 flex items-center justify-between">
|
|
<!-- Left Side: perPage Dropdown -->
|
|
<div class="flex items-center">
|
|
<select class="w-20 rounded-md border border-theme-primary bg-theme-background px-3 py-2 text-theme-primary shadow-sm focus:border-theme-accent focus:outline-none focus:ring-1 focus:ring-theme-accent sm:text-sm"
|
|
wire:model.live="perPage">
|
|
<option value="10">10</option>
|
|
<option value="20">20</option>
|
|
<option value="50">50</option>
|
|
</select>
|
|
<span class="ml-2 text-sm text-theme-secondary">{{ __('per page') }}</span>
|
|
</div>
|
|
|
|
<!-- Right Side: Paginator -->
|
|
{{ $profiles->links('livewire.long-paginator') }}
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Edit Accounts modal -->
|
|
@if ($editAccounts)
|
|
<x-jetstream.dialog-modal wire:key="modalEditAccounts" wire:model.live="modalEditAccounts" maxWidth="4xl" closeButton="true">
|
|
|
|
<x-slot name="title">
|
|
{{ __('Accounts') . ' ' . class_basename($editAccounts['model']) . ' ' . $editAccounts['id'] }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="mb-4 flex flex-wrap w-full items-center gap-4">
|
|
{{-- Photo on the left --}}
|
|
<div class="whitespace-no-wrap pr-4 text-sm leading-5 flex-shrink-0">
|
|
@if ($editAccounts['profile_photo_path'])
|
|
<div class="relative block cursor-pointer"
|
|
onclick="window.location='{{ route('profile.show_by_type_and_id', ['type' => strtolower(__(strtolower(class_basename($editAccounts['model'])))), 'id' => $editAccounts['id']]) }}'">
|
|
<img alt="profile"
|
|
class="mx-auto h-16 w-16 rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
|
|
src="{{ Storage::url($editAccounts['profile_photo_path']) }}" />
|
|
</div>
|
|
@else
|
|
{{-- Optional: Placeholder if no photo --}}
|
|
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-theme-surface text-theme-light">
|
|
<x-icon class="h-8 w-8" name="photograph" />
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='text-xl'>
|
|
<div class="text-lg font-semibold">
|
|
{{ $initAccounts['name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initAccounts['full_name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initAccounts['location'] }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex flex-wrap flex-1 min-w-[200px] justify-end gap-3">
|
|
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ App\Models\Language::where('lang_code', $editAccounts['lang_preference'])->value('flag') }}
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
</div>
|
|
|
|
<div class='text-sm'>
|
|
<div class="">
|
|
{{ __('Last login') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Last update') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Registered') . ': ' }}
|
|
</div>
|
|
@if ($initAccounts['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Inactive') . ': ' }}
|
|
</div>
|
|
@endif
|
|
@if ($initAccounts['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Deleted') . ': ' }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ $initAccounts['last_login_at'] ? \Carbon\Carbon::parse($initAccounts['last_login_at'])->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initAccounts['last_login_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initAccounts['updated_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initAccounts['created_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initAccounts['created_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
@if ($initAccounts['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initAccounts['inactive_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
@if ($initAccounts['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initAccounts['deleted_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if (!empty($initAccounts['deleted_at']) && \Carbon\Carbon::parse($editAccounts['deleted_at'])->isPast())
|
|
<div class="mb-6 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3 text-red-500">{{ __('Re-activate') }}</div>
|
|
<div class="flex-1">
|
|
<x-toggle id="re-activate" name="re-activate" negative disabled="true"
|
|
label="_('profile has been deleted') wire:model.live="reActivate" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif (!empty($initAccounts['inactive_at']) && \Carbon\Carbon::parse($initProfile['inactive_at'])->isPast())
|
|
<div class="mb-6 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3 text-red-500">{{ __('Re-activate') }}</div>
|
|
<div class="flex-1">
|
|
<x-toggle id="re-activate" name="re-activate" negative wire:model.live="reActivate" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="mb-6 flex flex-col space-y-4">
|
|
{{-- Header row --}}
|
|
<div class="flex gap-6 text-xs font-semibold text-gray-500 uppercase tracking-wider border-b pb-1">
|
|
<div class="w-12">{{ __('ID') }}</div>
|
|
<div class="w-36">{{ __('Name') }}</div>
|
|
<div class="w-20 text-right">{{ __('Balance') }}</div>
|
|
@if (timebank_config('admin_settings.admin_min_limit_edits', false))
|
|
<div class="w-28">{{ __('Min. limit') }}</div>
|
|
@endif
|
|
<div class="w-28">{{ __('Max. limit') }}</div>
|
|
<div class="w-32">{{ __('Inactive') }}</div>
|
|
</div>
|
|
|
|
@foreach ($editAccounts['accounts'] as $index => $account)
|
|
<div class="flex gap-6 items-start">
|
|
{{-- ID --}}
|
|
<div class="w-12 pt-2 text-sm text-gray-500">
|
|
{{ !empty($account['isNew']) ? __('New') : $account['id'] }}
|
|
</div>
|
|
|
|
{{-- Name --}}
|
|
<div class="w-36">
|
|
<x-input wire:model.lazy="editAccounts.accounts.{{ $index }}.rawName" />
|
|
<x-jetstream.input-error for="editAccounts.accounts.{{ $index }}.rawName" class="mt-1" />
|
|
</div>
|
|
|
|
{{-- Balance (read-only) --}}
|
|
<div class="w-20 pt-2 text-sm text-right transition-colors"
|
|
:class="$wire.editAccounts.accounts[{{ $index }}]?.markInactive && {{ $account['balance'] }} !== 0 ? 'text-red-600' : 'text-gray-700'">
|
|
{{ tbFormat($account['balance']) }}
|
|
</div>
|
|
|
|
|
|
@if (timebank_config('admin_settings.admin_min_limit_edits', false))
|
|
{{-- Min. limit --}}
|
|
<div class="w-28">
|
|
<div class="flex items-center gap-1">
|
|
<span class="text-sm text-gray-500 shrink-0">H</span>
|
|
<x-input type="number" wire:model.lazy="editAccounts.accounts.{{ $index }}.editLimitMin" class="w-full" />
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Max. limit --}}
|
|
<div class="w-28">
|
|
<div class="flex items-center gap-1">
|
|
<span class="text-sm text-gray-500 shrink-0">H</span>
|
|
<x-input type="number" wire:model.lazy="editAccounts.accounts.{{ $index }}.editLimitMax" class="w-full" />
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Inactive / Delete / Activate --}}
|
|
<div class="w-32 pt-1">
|
|
@if (!empty($account['isNew']))
|
|
{{-- New accounts can't be marked inactive yet --}}
|
|
@elseif ($account['inactiveAt'] && \Illuminate\Support\Carbon::parse($account['inactiveAt'])->isPast())
|
|
<div class="flex items-center gap-1">
|
|
<span class="text-sm transition-all"
|
|
:class="$wire.editAccounts.accounts[{{ $index }}]?.markActivate ? 'line-through text-theme-secondary' : 'text-red-500'">
|
|
{{ \Illuminate\Support\Carbon::parse($account['inactiveAt'])->format('Y-m-d') }}
|
|
</span>
|
|
<button type="button"
|
|
wire:click="$set('editAccounts.accounts.{{ $index }}.markActivate', {{ $account['markActivate'] ? 'false' : 'true' }})"
|
|
title="{{ __('Activate') }}"
|
|
class="ml-1 text-theme-secondary hover:text-red-600 shrink-0">
|
|
<x-icon name="x-circle" mini class="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
@elseif (empty($account['hasTransactions']))
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox"
|
|
wire:model.live="editAccounts.accounts.{{ $index }}.markDelete"
|
|
class="rounded border-gray-300 text-red-600 shadow-sm focus:ring-red-500" />
|
|
<span class="text-sm text-red-600">{{ __('Delete account') }}</span>
|
|
</label>
|
|
@else
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
<input type="checkbox"
|
|
wire:model.live="editAccounts.accounts.{{ $index }}.markInactive"
|
|
class="rounded border-gray-300 text-red-600 shadow-sm focus:ring-red-500" />
|
|
<span class="text-sm text-gray-600">{{ __('Mark inactive') }}</span>
|
|
</label>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
|
|
{{-- New account button --}}
|
|
<div class="pt-2 flex items-center gap-3">
|
|
<x-jetstream.secondary-button wire:click="addNewAccount" type="button">
|
|
{{ __('New account') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
{{-- Reverse transaction (Bank profiles only) — looks up a transaction
|
|
by ID across ALL of this profile's accounts, rather than a
|
|
per-account button. --}}
|
|
@if (getActiveProfileType() === 'Bank')
|
|
<x-jetstream.secondary-button type="button" wire:click="openReversalModal">
|
|
{{ __('Reverse transaction') }}
|
|
</x-jetstream.secondary-button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4 flex flex-col space-y-3">
|
|
<div class="text-sm font-semibold text-gray-700">{{ __('Profile balance limits') }}</div>
|
|
<div class="flex gap-6 items-start">
|
|
@if (timebank_config('admin_settings.admin_min_limit_edits', false))
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm text-gray-500 w-20">{{ __('Min. limit') }}</span>
|
|
<span class="text-sm text-gray-500 shrink-0">H</span>
|
|
<x-input type="number" wire:model.lazy="editAccounts.profileLimitMin" class="!w-28 shrink-0" />
|
|
</div>
|
|
@endif
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm text-gray-500 w-20">{{ __('Max. limit') }}</span>
|
|
<span class="text-sm text-gray-500 shrink-0">H</span>
|
|
<x-input type="number" wire:model.lazy="editAccounts.profileLimitMax" class="!w-28 shrink-0" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Sum of all accounts') }}</div>
|
|
<div class="flex-1">
|
|
{{ tbFormat($editAccounts['totals']['sumBalances']) }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Receivable limit') }}</div>
|
|
<div class="flex-1">
|
|
{{ !empty($editAccounts['accounts'][0]) ? tbFormat($editAccounts['accounts'][0]['limitReceivable']) : '-' }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Total transfers (ever)') }}</div>
|
|
<div class="flex-1">
|
|
{{ $editAccounts['totals']['transfers'] }}
|
|
@if ($editAccounts['totals']['transfers'] > 0)
|
|
({{ $editAccounts['totals']['transfersGiven'] }} {{ __('paid') }},
|
|
{{ $editAccounts['totals']['transfersReceived'] }} {{ __('received') }})
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Transfers') }}
|
|
{{ __(timebank_config('account_info.account_totals.countTransfersSince_humanReadable')) }}</div>
|
|
<div class="flex-1">
|
|
{{ $editAccounts['totalsPastYear']['transfers'] }}
|
|
@if ($editAccounts['totalsPastYear']['transfers'] > 0)
|
|
({{ $editAccounts['totalsPastYear']['transfersGiven'] }} {{ __('paid') }},
|
|
{{ $editAccounts['totalsPastYear']['transfersReceived'] }} {{ __('received') }})
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Last transfer') }}</div>
|
|
<div class="flex-1">
|
|
@if (empty($editAccounts['totals']['lastTransferDate']))
|
|
{{ __('No transfers yet') }}
|
|
@else
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($editAccounts['totals']['lastTransferDate']), config('app.timezone'))->diffForHumans() }}
|
|
({{ $editAccounts['totals']['lastTransferDate'] }})
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="h-6"></div>
|
|
|
|
@if ($editAccountsChanged)
|
|
<div class="mb-2 text-sm text-red-600">{{ __('The profile owner will be notified about this update.') }}</div>
|
|
<div class="mb-4 text-sm text-red-600">{{ __('This update can not be undone!') }}</div>
|
|
@endif
|
|
|
|
{{-- Warning: accounts with balance being marked inactive --}}
|
|
@php
|
|
$inactiveWithBalance = collect($editAccounts['accounts'])->filter(
|
|
fn($a) => !empty($a['markInactive']) && ($a['balance'] ?? 0) !== 0
|
|
);
|
|
@endphp
|
|
@if ($inactiveWithBalance->isNotEmpty())
|
|
<div class="mb-4 space-y-1">
|
|
@foreach ($inactiveWithBalance as $a)
|
|
<div class="text-sm text-red-600">
|
|
⚠ {{ __('Account :name still has a balance of :balance. These :currency will become inaccessible once the account is set to inactive.', [
|
|
'name' => account_name($a['rawName']),
|
|
'balance' => tbFormat($a['balance']),
|
|
'currency' => platform_currency_name_plural(),
|
|
]) }}
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
<div class="w-1/2">
|
|
<x-input label="{!! __('messages.confirm_input') !!}"
|
|
placeholder="{{ __('Confirmation keyword') }}"
|
|
wire:model.live="confirmString" />
|
|
</div>
|
|
<x-errors />
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button :disabled="$buttonDisabled" class="ml-3"
|
|
wire:click.prevent="updateAccounts"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Save') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Issue Reversal modal -->
|
|
@if ($modalReversal)
|
|
<x-jetstream.dialog-modal wire:key="modalReversal" wire:model.live="modalReversal" maxWidth="2xl" closeButton="true">
|
|
<x-slot name="title">
|
|
{{ __('Issue Reversal') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<p class="mb-4 text-sm text-theme-secondary">
|
|
{{ __('Enter the number of the incorrect transaction to reverse. Only transactions belonging to this profile can be looked up. Two new transactions will be created: the recipient will pay the amount back to the bank, and the bank will pay the corrected amount to the sender.') }}
|
|
</p>
|
|
|
|
<div class="mb-4">
|
|
<label class="mb-1 block text-sm font-medium text-theme-primary">{{ __('Transaction number') }}</label>
|
|
<div class="flex items-start gap-2">
|
|
<div class="w-48 min-w-0">
|
|
<x-input type="text" inputmode="numeric" pattern="[0-9]*"
|
|
wire:model="reversalLookupId"
|
|
wire:keydown.enter.prevent="lookupReversalTransaction"
|
|
placeholder="{{ __('e.g. 66530') }}"
|
|
class="w-full" />
|
|
</div>
|
|
<x-jetstream.secondary-button type="button" wire:click="lookupReversalTransaction" wire:loading.attr="disabled" class="whitespace-nowrap shrink-0">
|
|
{{ __('Look up') }}
|
|
</x-jetstream.secondary-button>
|
|
</div>
|
|
@if ($reversalLookupError)
|
|
<p class="mt-1 text-sm text-red-600">{{ $reversalLookupError }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
@if ($reversalSelectedTransactionId)
|
|
<div class="mb-4 rounded-md bg-gray-50 border border-gray-200 p-3 text-sm text-theme-primary">
|
|
<div class="font-medium">{{ __('Transaction #:id', ['id' => $reversalSelectedTransactionId]) }}</div>
|
|
<div class="text-theme-secondary">{{ $reversalCreatedAt }}</div>
|
|
<div class="mt-1">{{ tbFormat((int) $reversalAmount) }} ({{ $reversalDirection }})</div>
|
|
</div>
|
|
@if ($reversalSenderName && $reversalRecipientName)
|
|
<div class="mb-4 rounded-md bg-yellow-50 border border-yellow-200 p-3 text-sm text-yellow-800">
|
|
{{ __('This reversal will take the amount back from :recipient and return it to :sender.', ['recipient' => $reversalRecipientName, 'sender' => $reversalSenderName]) }}
|
|
</div>
|
|
@endif
|
|
|
|
<p class="mb-4 text-xs text-theme-secondary">{{ __('A reversal always covers the full amount of the transaction.') }}</p>
|
|
|
|
<div class="mb-4">
|
|
<label class="mb-1 block text-sm font-medium text-theme-primary">{{ __('Reverse requested by') }}</label>
|
|
<x-select :clearable="false" :searchable="false" class="w-64"
|
|
placeholder="{{ __('Select who requested this') }}"
|
|
wire:model="reversalRequestedBy">
|
|
@foreach ($reversalRequesterOptions as $option)
|
|
<x-select.option label="{{ $option['full_name'] }}" value="{{ $option['key'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
<x-jetstream.input-error for="reversalRequestedBy" class="mt-1" />
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="mb-1 block text-sm font-medium text-theme-primary">{{ __('Description') }}</label>
|
|
<div class="text-sm text-theme-primary">
|
|
{{ $reversalDescription }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-2 text-sm text-red-600">{{ __('The profile will be notified about this reversal.') }}</div>
|
|
<div class="mb-4 text-sm text-red-600">{{ __('This action can not be undone!') }}</div>
|
|
|
|
<div class="w-1/2">
|
|
<x-input label="{!! __('messages.confirm_input') !!}"
|
|
placeholder="{{ __('Confirmation keyword') }}"
|
|
wire:model.live="reversalConfirmString" />
|
|
<x-jetstream.input-error for="confirmString" class="mt-1" />
|
|
</div>
|
|
@endif
|
|
|
|
<x-errors />
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.danger-button class="ml-3"
|
|
:disabled="!$reversalSelectedTransactionId"
|
|
wire:click.prevent="issueReversal"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Issue Reversal') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Edit Profile modal -->
|
|
@if ($editProfile)
|
|
<x-jetstream.dialog-modal wire:key="modalEditProfile" wire:model.live="modalEditProfile" maxWidth="4xl" closeButton="true">
|
|
|
|
<x-slot name="title">
|
|
{{ __('Profile info') . ': ' . class_basename($editProfile['model']) . ' ' . $editProfile['id'] }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="mb-4 flex flex-wrap w-full items-center gap-4">
|
|
{{-- Photo on the left --}}
|
|
<div class="whitespace-no-wrap pr-4 text-sm leading-5 flex-shrink-0">
|
|
@if ($editProfile['profile_photo_path'])
|
|
<div class="relative block cursor-pointer"
|
|
onclick="window.location='{{ route('profile.show_by_type_and_id', ['type' => strtolower(__(strtolower(class_basename($editProfile['model'])))), 'id' => $editProfile['id']]) }}'">
|
|
<img alt="profile"
|
|
class="mx-auto h-16 w-16 rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
|
|
src="{{ $editProfile['profile_photo_path'] ? Storage::url($editProfile['profile_photo_path']) : Storage::url(timebank_config('profiles.' . strtolower(class_basename($editProfile['model'])) . '.profile_photo_path_default')) }}" />
|
|
</div>
|
|
@else
|
|
{{-- Optional: Placeholder if no photo --}}
|
|
<div
|
|
class="flex h-16 w-16 items-center justify-center rounded-full bg-theme-surface text-theme-light">
|
|
<x-icon class="h-8 w-8" name="photograph" />
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='text-xl'>
|
|
<div class="text-lg font-semibold">
|
|
{{ $initProfile['name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initProfile['full_name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initProfile['location'] }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex flex-wrap flex-1 min-w-[200px] justify-end gap-3">
|
|
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ App\Models\Language::where('lang_code', $editProfile['lang_preference'])->value('flag') }}
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
</div>
|
|
|
|
<div class='text-sm'>
|
|
<div class="">
|
|
{{ __('Last login') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Last update') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Registered') . ': ' }}
|
|
</div>
|
|
@if ($initProfile['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Inactive') . ': ' }}
|
|
</div>
|
|
@endif
|
|
@if ($initProfile['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Deleted') . ': ' }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ $initProfile['last_login_at'] ? \Carbon\Carbon::parse($initProfile['last_login_at'])->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initProfile['last_login_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initProfile['updated_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initProfile['created_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initProfile['created_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
@if ($initProfile['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initProfile['inactive_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
@if ($initProfile['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initProfile['deleted_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!empty($initProfile['deleted_at']) && \Illuminate\Support\Carbon::parse($editProfile['deleted_at'])->isPast())
|
|
<div class="mb-6 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Re-activate') }}</div>
|
|
<div class="flex-1">
|
|
<x-toggle disabled id="re-activate"
|
|
label="{{ _('Disabled because profile has been deleted') }}"
|
|
name="re-activate" negative wire:model.live="reActivate" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@elseif (!empty($initProfile['inactive_at']) && \Illuminate\Support\Carbon::parse($initProfile['inactive_at'])->isPast())
|
|
<div class="mb-6 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3 text-red-500">{{ __('Re-activate') }}</div>
|
|
<div class="flex-1">
|
|
<x-toggle id="re-activate" name="re-activate" negative
|
|
wire:model.live="reActivate" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="mb-2 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Username') }}</div>
|
|
<div class="flex-1">
|
|
<x-input placeholder="{{ __('Login or username') }}"
|
|
wire:model.lazy="editProfile.name" />
|
|
</div>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Full name') }}</div>
|
|
<div class="flex-1">
|
|
<x-input placeholder="{{ __('Full name') }}" wire:model.lazy="editProfile.full_name" />
|
|
</div>
|
|
</div>
|
|
@if (isset($editProfile['modelClass']) && $editProfile['modelClass'] === 'App\Models\Bank')
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Bank level') }}</div>
|
|
<div class="flex-1">
|
|
<x-select placeholder="{{ __('Select bank level') }}" wire:model.lazy="editProfile.level">
|
|
<x-select.option label="{{ __('Non public system bank') }}" value="1" />
|
|
<x-select.option label="{{ __('Public bank') }}" value="2" />
|
|
</x-select>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<div class="flex">
|
|
@if ($editProfile['email_verified_at'])
|
|
<div class="w-1/3">{{ __('Email') }} </div>
|
|
@else
|
|
<div class="w-1/3 text-red-500">{{ __('Email (not verified)') }} </div>
|
|
@endif
|
|
<div class="flex-1 flex items-center gap-2">
|
|
<div class="flex-1">
|
|
<x-input placeholder="{{ __('Email address') }}" wire:model.lazy="editProfile.email" />
|
|
</div>
|
|
@php
|
|
$emailPending = $editProfile['email_suppression_pending'] ?? null;
|
|
$emailIsSuppressed = $emailPending !== null
|
|
? $emailPending
|
|
: ($editProfile['email'] ? \App\Models\MailingBounce::isSuppressed($editProfile['email']) : false);
|
|
@endphp
|
|
@if ($emailIsSuppressed)
|
|
<x-jetstream.danger-button
|
|
title="{{ __('Unsuppress email — click to allow emails to this address again') }}"
|
|
wire:click="toggleEmailSuppression"
|
|
wire:target="toggleEmailSuppression"
|
|
wire:loading.attr="disabled">
|
|
<x-icon class="h-5 w-5" name="exclamation-triangle" solid />
|
|
</x-jetstream.danger-button>
|
|
@else
|
|
<x-jetstream.secondary-button
|
|
title="{{ __('Suppress email — block all emails to this address') }}"
|
|
wire:click="toggleEmailSuppression"
|
|
wire:target="toggleEmailSuppression"
|
|
wire:loading.attr="disabled">
|
|
<x-icon class="h-5 w-5" name="exclamation-triangle" />
|
|
</x-jetstream.secondary-button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Settings Row -->
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Message Settings') }}</div>
|
|
<div class="flex-1 flex flex-col gap-2">
|
|
<!-- Line 1: System, Payment, Star -->
|
|
<div class="flex gap-4 items-center">
|
|
<x-checkbox secondary id="msg_system" label="{{ __('System') }}" wire:model.live="editMessageSettings.system_message" :checked="(bool)($editMessageSettings['system_message'] ?? false)" />
|
|
@if ($editProfile['model'] != 'admin')
|
|
<x-checkbox secondary id="msg_payment" label="{{ __('Payment') }}" wire:model.live="editMessageSettings.payment_received" :checked="(bool)($editMessageSettings['payment_received'] ?? false)" />
|
|
<x-checkbox secondary id="msg_star" label="{{ __('Star') }}" wire:model.live="editMessageSettings.star_received" :checked="(bool)($editMessageSettings['star_received'] ?? false)" />
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Line 2: Local Newsletter, General Newsletter -->
|
|
<div class="flex gap-4 items-center">
|
|
<x-checkbox secondary id="msg_local_newsletter" label="{{ __('Local newsletter') }}" wire:model.live="editMessageSettings.local_newsletter" :checked="(bool)($editMessageSettings['local_newsletter'] ?? false)" />
|
|
<x-checkbox secondary id="msg_general_newsletter" label="{{ __('General newsletter') }}" wire:model.live="editMessageSettings.general_newsletter" :checked="(bool)($editMessageSettings['general_newsletter'] ?? false)" />
|
|
</div>
|
|
|
|
<!-- Line 3: Personal Chat, Group Chat, Unread Delay -->
|
|
@if ($editProfile['model'] != 'admin')
|
|
<div class="flex gap-4 items-center">
|
|
<x-checkbox secondary id="msg_personal_chat" label="{{ __('Personal chat') }}" wire:model.live="editMessageSettings.personal_chat" :checked="(bool)($editMessageSettings['personal_chat'] ?? false)" />
|
|
<x-checkbox secondary id="msg_group_chat" label="{{ __('Group chat') }}" wire:model.live="editMessageSettings.group_chat" :checked="(bool)($editMessageSettings['group_chat'] ?? false)" />
|
|
<div class="flex items-center gap-2">
|
|
<label for="msg_unread_delay" class="text-sm text-theme-primary">{{ __('Unread delay') }}</label>
|
|
<input type="number" id="msg_unread_delay" min="0" max="1440" value="{{ (int)($editMessageSettings['chat_unread_delay'] ?? 0) }}" wire:model.live="editMessageSettings.chat_unread_delay" class="w-12 px-2 py-1 text-sm border border-theme-border rounded bg-white text-theme-primary" placeholder="0" />
|
|
<span class="text-xs text-theme-secondary">min</span>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Short introduction') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="3" wire:model.lazy="editProfile.about_short" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Long introduction') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="3" wire:model.lazy="editProfile.about" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ trans_with_platform('Motivation to @PLATFORM_NAME_SHORT@') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="3" wire:model.lazy="editProfile.motivation" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Website') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="3" wire:model.lazy="editProfile.website" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
@if ($editProfile['phone_public'])
|
|
@if ($editProfile['model'] == 'user')
|
|
<div class="w-1/3">{{ __('Phone (only public for friends!)') }}</div>
|
|
@else
|
|
<div class="w-1/3">{{ __('Phone (public for ' . platform_users() . ')') }}</div>
|
|
@endif
|
|
@else
|
|
<div class="w-1/3">{{ __('Phone (not public!)') }}</div>
|
|
@endif
|
|
<div class="flex-1">
|
|
<x-input placeholder="{{ __('+31612345678') }}" wire:model.lazy="editProfile.phone" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Admin comments') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="5" wire:model.lazy="editProfile.comment" />
|
|
</div>
|
|
</div>
|
|
|
|
|
|
@if (isset($editProfile['modelClass']) && class_basename($editProfile['modelClass']) === 'Organization')
|
|
<div class="flex mt-4">
|
|
<div class="w-1/3"></div>
|
|
<div class="flex-1">
|
|
<x-jetstream.danger-button
|
|
wire:click="openConvertToUserModal({{ $editProfile['id'] }}, '{{ addslashes($editProfile['modelClass']) }}')"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Convert to user profile') }}
|
|
</x-jetstream.danger-button>
|
|
</div>
|
|
</div>
|
|
@elseif (isset($editProfile['modelClass']) && class_basename($editProfile['modelClass']) === 'User')
|
|
<div class="flex mt-4">
|
|
<div class="w-1/3"></div>
|
|
<div class="flex-1">
|
|
<x-jetstream.danger-button
|
|
wire:click="openConvertToOrganizationModal({{ $editProfile['id'] }}, '{{ addslashes($editProfile['modelClass']) }}')"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Convert to organization profile') }}
|
|
</x-jetstream.danger-button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
@php
|
|
// Check if there are meaningful changes beyond just comments
|
|
$meaningfulChanges = false;
|
|
if ($editProfileChanged) {
|
|
foreach ($editProfile as $field => $value) {
|
|
// Skip internal/system fields and comment field
|
|
if (in_array($field, ['comment', 'model', 'modelClass', 'location', 'email_suppression_pending'])) {
|
|
continue;
|
|
}
|
|
// Check if field exists in initProfile and has changed
|
|
if (array_key_exists($field, $initProfile)) {
|
|
$currentVal = (string)$value;
|
|
$initialVal = (string)($initProfile[$field] ?? '');
|
|
if ($currentVal !== $initialVal) {
|
|
$meaningfulChanges = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Also show if message settings changed
|
|
$shouldShowNotification = $meaningfulChanges || ($editMessageSettingsChanged ?? false);
|
|
@endphp
|
|
|
|
@if ($shouldShowNotification)
|
|
<div class="mt-3 text-red-500">
|
|
{{ __('The profile owner will be notified about this update.') }}
|
|
|
|
@if (count($editProfileMessages) > 0)
|
|
@foreach ($editProfileMessages as $message)
|
|
<div class="">
|
|
{{ $message }}
|
|
</div>
|
|
@endforeach
|
|
@endif
|
|
</div>
|
|
|
|
<div class="mt-3 text-red-500">
|
|
{{ __('This update can not be undone!') }}
|
|
</div>
|
|
@endif
|
|
<div class="mt-3 w-1/3">
|
|
<x-input label="{!! __('messages.confirm_input') !!}" placeholder="{{ __('Confirmation keyword') }}"
|
|
wire:model.live="confirmString" />
|
|
</div>
|
|
<x-errors />
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button :disabled="$buttonDisabled" class="ml-3"
|
|
wire:click.prevent="updateProfile({{ $editProfile['id'] }})"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Update') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Edit Attach Profiles modal -->
|
|
@if ($editAttachProfile)
|
|
<x-jetstream.dialog-modal wire:key="modalAttachProfile" wire:model.live="modalAttachProfile" maxWidth="4xl" closeButton="true">
|
|
|
|
<x-slot name="title">
|
|
{{ __('Profile links') . ': ' . class_basename($editAttachProfile['model']) . ' ' . $editAttachProfile['id'] }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="mb-4 flex flex-wrap w-full items-center gap-4">
|
|
{{-- Photo on the left --}}
|
|
<div class="whitespace-no-wrap pr-4 text-sm leading-5 flex-shrink-0">
|
|
@if ($editAttachProfile['profile_photo_path'])
|
|
<div class="relative block cursor-pointer"
|
|
onclick="window.location='{{ route('profile.show_by_type_and_id', ['type' => strtolower(__(strtolower(class_basename($editAttachProfile['model'])))), 'id' => $editAttachProfile['id']]) }}'">
|
|
<img alt="profile"
|
|
class="mx-auto h-16 w-16 rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
|
|
src="{{ $editAttachProfile['profile_photo_path'] ? Storage::url($editAttachProfile['profile_photo_path']) : Storage::url(timebank_config('profiles.' . strtolower(class_basename($editAttachProfile['model'])) . '.profile_photo_path_default')) }}" />
|
|
</div>
|
|
@else
|
|
{{-- Optional: Placeholder if no photo --}}
|
|
<div
|
|
class="flex h-16 w-16 items-center justify-center rounded-full bg-theme-surface text-theme-light">
|
|
<x-icon class="h-8 w-8" name="photograph" />
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='text-xl'>
|
|
<div class="text-lg font-semibold">
|
|
{{ $initAttachProfile['name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initAttachProfile['full_name'] }}
|
|
</div>
|
|
<div class="text-sm font-normal text-theme-secondary">
|
|
{{ $initAttachProfile['location'] }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex flex-wrap flex-1 min-w-[200px] justify-end gap-3">
|
|
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ App\Models\Language::where('lang_code', $initAttachProfile['lang_preference'])->value('flag') }}
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
<div class="">
|
|
</div>
|
|
</div>
|
|
|
|
<div class='text-sm'>
|
|
<div class="">
|
|
{{ __('Last login') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Last update') . ': ' }}
|
|
</div>
|
|
<div class="">
|
|
{{ __('Registered') . ': ' }}
|
|
</div>
|
|
@if ($initAttachProfile['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Inactive') . ': ' }}
|
|
</div>
|
|
@endif
|
|
@if ($initAttachProfile['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ __('Deleted') . ': ' }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class='pr-3 text-sm'>
|
|
<div class="">
|
|
{{ $initAttachProfile['last_login_at'] ? \Carbon\Carbon::parse($initAttachProfile['last_login_at'])->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initAttachProfile['last_login_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initAttachProfile['updated_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
<div class="">
|
|
{{ $initAttachProfile['created_at'] ? \Carbon\Carbon::createFromTimeStamp(strtotime($initAttachProfile['created_at']), config('app.timezone'))->diffForHumans() : '-' }}
|
|
</div>
|
|
@if ($initAttachProfile['inactive_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initAttachProfile['inactive_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
@if ($initAttachProfile['deleted_at'])
|
|
<div class="text-red-500">
|
|
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($initAttachProfile['deleted_at']), config('app.timezone'))->diffForHumans() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-6 flex">
|
|
<div class="mt-2 w-1/3">{{ __('Linked profiles') }}</div>
|
|
<div class="flex-1">
|
|
@error('editAttachProfile.profiles')
|
|
<div class="mb-3 rounded-md border border-red-300 bg-red-50 px-4 py-2 text-sm text-red-700">
|
|
<x-icon name="exclamation-triangle" class="inline h-4 w-4 mr-1" />
|
|
{{ $message }}
|
|
</div>
|
|
@enderror
|
|
@foreach ($editAttachProfile['profiles'] as $profileIndex => $profile)
|
|
@php
|
|
$isRemoved = !empty($profile['removed']);
|
|
// For Org/Bank modals the role type is the parent; for User modals it's the linked profile's own type.
|
|
$roleType = ucfirst($editAttachProfile['model']) === 'User'
|
|
? ($profile['type'] ?? '')
|
|
: ucfirst($editAttachProfile['model']);
|
|
$roleOptions = match($roleType) {
|
|
'Organization' => $orgRoleOptions,
|
|
'Bank' => $bankRoleOptions,
|
|
'Admin' => $adminRoleOptions,
|
|
default => [],
|
|
};
|
|
$showRoleSelect = !$isRemoved && count($roleOptions) > 0;
|
|
@endphp
|
|
<div class="{{ $isRemoved ? 'opacity-50 text-theme-secondary' : '' }} mb-3 flex items-stretch gap-6">
|
|
{{-- Photo spanning both rows --}}
|
|
<div class="flex-shrink-0">
|
|
@if ($profile['profile_photo_path'] ?? false)
|
|
<img alt="profile"
|
|
class="{{ $isRemoved ? 'opacity-50 grayscale' : '' }} h-full w-16 rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
|
|
src="{{ Storage::url($profile['profile_photo_path']) }}" />
|
|
@else
|
|
<div class="h-full w-16"></div>
|
|
@endif
|
|
</div>
|
|
{{-- Name + role stacked --}}
|
|
<div class="flex flex-1 flex-col justify-between">
|
|
<div class="flex items-center">
|
|
<div class="flex-1 text-sm">{{ ($profile['username'] ?? '') . ' - ' . ucfirst($profile['name'] ?? '') }}</div>
|
|
<div class="text-right">
|
|
@if (!$isRemoved)
|
|
<button class="text-theme-primary hover:text-red-600" type="button"
|
|
wire:click="removeAttachedProfile({{ $profile['id'] }}, '{{ $profile['type'] }}')">
|
|
<x-icon mini name="x-circle" />
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@if ($showRoleSelect)
|
|
@php $fixedRole = in_array($roleType, ['User']); @endphp
|
|
<div>
|
|
<x-select :searchable="false" :disabled="$fixedRole" wire:model.live="editAttachProfile.profiles.{{ $profileIndex }}.role">
|
|
@foreach ($roleOptions as $opt)
|
|
<x-select.option label="{{ $opt['name'] }}{{ !empty($opt['is_system']) ? ' (' . __('system') . ')' : '' }}" value="{{ $opt['id'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="mt-2 w-1/3">{{ __('Attach new profile') }}</div>
|
|
<div class="flex-1">
|
|
<livewire:profile.select-profile :typesAvailable="$newTypesAvailable">
|
|
@if (!empty($editAttachProfile['newProfile']))
|
|
@php
|
|
$newProfileModel = class_basename($editAttachProfile['newProfile']['type'] ?? '');
|
|
$parentModel = ucfirst($editAttachProfile['model'] ?? '');
|
|
$newRoleType = $newProfileModel !== 'User' ? $newProfileModel : $parentModel;
|
|
$newRoleOptions = match($newRoleType) {
|
|
'Organization' => $orgRoleOptions,
|
|
'Bank' => $bankRoleOptions,
|
|
'Admin' => $adminRoleOptions,
|
|
default => [],
|
|
};
|
|
@endphp
|
|
@if (count($newRoleOptions) > 0)
|
|
@php $newRoleFixed = in_array($newRoleType, ['User']); @endphp
|
|
<div class="mt-2 mb-6 max-w-md">
|
|
<x-select class="w-full" :searchable="false" :disabled="$newRoleFixed" wire:model.live="editAttachProfile.newProfile.role" placeholder="{{ __('Select a role') }}">
|
|
@foreach ($newRoleOptions as $opt)
|
|
<x-select.option label="{{ $opt['name'] }}{{ !empty($opt['is_system']) ? ' (' . __('system') . ')' : '' }}" value="{{ $opt['id'] }}" />
|
|
@endforeach
|
|
</x-select>
|
|
@error('editAttachProfile.newProfile.role')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex">
|
|
<div class="mt-2 w-1/3">{{ __('Admin comments') }}</div>
|
|
<div class="flex-1">
|
|
<x-textarea rows="5" wire:model.lazy="editAttachProfile.comment" />
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
@if ($editAttachProfileChanged)
|
|
<div class="mt-3 text-red-500">
|
|
{{ __('The profile owner will be notified about this update.') }}
|
|
</div>
|
|
|
|
<div class="mt-3 text-red-500">
|
|
{{ __('This update can not be undone!') }}
|
|
</div>
|
|
@endif
|
|
<div class="mt-3 w-1/3">
|
|
<x-input label="{!! __('messages.confirm_input') !!}" placeholder="{{ __('Confirmation keyword') }}"
|
|
wire:model.live="confirmString" />
|
|
</div>
|
|
<x-errors />
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button :disabled="$buttonDisabled" class="ml-3"
|
|
wire:click.prevent="attachProfile({{ $editAttachProfile['id'] }})"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Update') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!----Restore Profile modal ---->
|
|
@if ($modalRestoreProfile && $restoreProfileData)
|
|
<x-jetstream.dialog-modal wire:model.live="modalRestoreProfile" maxWidth="2xl">
|
|
<x-slot name="title">
|
|
{{ __('Are you sure?') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="font-semibold text-red-600">
|
|
{{ __('Do you want to restore this profile?') }}
|
|
</div>
|
|
|
|
{{-- Profile Info --}}
|
|
<div class="my-4 p-4 bg-gray-50 rounded-lg">
|
|
<div class="font-semibold text-gray-900">{{ $restoreProfileData['name'] }}</div>
|
|
<div class="text-sm text-gray-600">{{ $restoreProfileData['full_name'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ $restoreProfileData['email'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ __('Type') }}: {{ $restoreProfileData['type'] }}</div>
|
|
</div>
|
|
|
|
<div class="mb-2 flex flex-col space-y-2">
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Deleted') }}:</div>
|
|
<div class="flex-1">{{ $restoreProfileData['deletedAt'] }}</div>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Time remaining') }}:</div>
|
|
<div class="flex-1">{{ $restoreProfileData['timeRemaining'] }}</div>
|
|
</div>
|
|
<div class="flex">
|
|
<div class="w-1/3">{{ __('Grace period expires') }}:</div>
|
|
<div class="flex-1">{{ $restoreProfileData['gracePeriodExpiry'] }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
{{ __('This profile will be fully restored and the user will be able to log in again. All balance handling preferences will be cleared.') }}
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<x-jetstream.label value="{{ __('Admin password confirmation') }}" />
|
|
<x-jetstream.input type="password" class="mt-1 block w-3/4"
|
|
placeholder="{{ __('Your admin password') }}"
|
|
wire:model="adminPassword" />
|
|
<x-jetstream.input-error for="adminPassword" class="mt-2" />
|
|
</div>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.danger-button
|
|
class="ml-3"
|
|
wire:click="restoreProfile"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Restore profile') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!----Delete Profile modal ---->
|
|
@if ($modalDeleteProfile && $deleteProfileData)
|
|
<x-jetstream.dialog-modal wire:model.live="modalDeleteProfile" maxWidth="4xl">
|
|
<x-slot name="title">
|
|
{{ __('Delete this profile?') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="font-semibold text-red-600">
|
|
{{ __('Are you sure you want to delete this profile? This step is irreversible.') }}
|
|
</div>
|
|
|
|
{{-- Profile Info --}}
|
|
<div class="my-4 p-4 bg-gray-50 rounded-lg">
|
|
<div class="font-semibold text-gray-900">{{ $deleteProfileData['name'] }}</div>
|
|
<div class="text-sm text-gray-600">{{ $deleteProfileData['full_name'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ $deleteProfileData['email'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ __('Type') }}: {{ $deleteProfileData['type'] }}</div>
|
|
</div>
|
|
|
|
<div class="my-3">
|
|
{{ trans_with_platform('Once your profile is deleted, all of its balance totals and data will be permanently deleted. All your transactions will be anonymized, also in the online transaction overviews and statements of other @PLATFORM_NAME@ users.') }}
|
|
</div>
|
|
|
|
{{-- Account Balances Overview --}}
|
|
@if($deleteProfileData['accounts'] && count($deleteProfileData['accounts']) > 0)
|
|
<div class="mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
|
|
<h4 class="text-sm font-semibold text-gray-700 mb-3">{{ __('Your accounts') }}</h4>
|
|
|
|
<div class="space-y-2">
|
|
@foreach($deleteProfileData['accounts'] as $account)
|
|
<div class="flex justify-between items-center text-sm">
|
|
<span class="text-gray-600">{{ $account['name'] }}</span>
|
|
<span class="font-medium {{ $account['balance'] < 0 ? 'text-red-600' : 'text-gray-900' }}">
|
|
{{ $account['balanceFormatted'] }}
|
|
</span>
|
|
</div>
|
|
@endforeach
|
|
|
|
<div class="pt-2 mt-2 border-t border-gray-300 flex justify-between items-center">
|
|
<span class="text-sm font-semibold text-gray-700">{{ __('Total balance') }}</span>
|
|
<span class="text-base font-bold {{ $deleteProfileData['totalBalance'] < 0 ? 'text-red-600' : 'text-gray-900' }}">
|
|
{{ tbFormat($deleteProfileData['totalBalance']) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Central Bank Warning --}}
|
|
@if($deleteProfileData['isCentralBank'])
|
|
<div class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-start">
|
|
<svg class="w-5 h-5 text-red-600 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<div>
|
|
<div class="text-sm font-semibold text-red-800">{{ __('Central bank cannot be deleted') }}</div>
|
|
<p class="mt-1 text-sm text-red-700">{{ __('This is the central bank (level 0) and cannot be removed from the system. Central banks are essential for currency creation and management.') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Final Admin Warning --}}
|
|
@if($deleteProfileData['isFinalAdmin'])
|
|
<div class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-start">
|
|
<svg class="w-5 h-5 text-red-600 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<div>
|
|
<div class="text-sm font-semibold text-red-800">{{ __('Final administrator cannot be deleted') }}</div>
|
|
<p class="mt-1 text-sm text-red-700">{{ __('At least one administrator account must remain active in the system.') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Negative Balance Warning --}}
|
|
@if($deleteProfileData['hasNegativeBalance'])
|
|
<div class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-start">
|
|
<svg class="w-5 h-5 text-red-600 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<div>
|
|
<div class="text-sm font-semibold text-red-800">{{ __('Cannot delete profile with negative balance') }}</div>
|
|
<p class="mt-1 text-sm text-red-700">{{ __('You must settle all debts before you can delete your profile. Please ensure all your account balances are zero or positive.') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Balance Handling Options --}}
|
|
@if($deleteProfileData['showBalanceOptions'] && $deleteProfileData['totalBalance'] != 0 && !$deleteProfileData['hasNegativeBalance'] && !$deleteProfileData['isCentralBank'] && !$deleteProfileData['isFinalAdmin'])
|
|
<div class="mt-6">
|
|
<x-jetstream.label for="balanceHandlingOption" value="{{ __('What would you like to do with your remaining balance?') }}" />
|
|
|
|
<div class="mt-3 space-y-3">
|
|
<label class="flex items-start">
|
|
<input type="radio" wire:model.live="balanceHandlingOption" value="donate" class="mt-1 text-theme-brand focus:ring-theme-brand">
|
|
<span class="ml-3">
|
|
<span class="block text-sm font-medium text-theme-primary">{{ __('Donate to an organization') }}</span>
|
|
<span class="block text-sm text-theme-muted">{{ __('Transfer your remaining balance to an organization of your choice') }}</span>
|
|
</span>
|
|
</label>
|
|
|
|
<label class="flex items-start">
|
|
<input type="radio" wire:model.live="balanceHandlingOption" value="delete" class="mt-1 text-theme-brand focus:ring-theme-brand">
|
|
<span class="ml-3">
|
|
<span class="block text-sm font-medium text-theme-primary">{{ __('Delete balance') }}</span>
|
|
<span class="block text-sm text-theme-muted">{{ __('Permanently delete your balance total') }}</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
@if($balanceHandlingOption === 'donate')
|
|
<div class="mt-4">
|
|
<livewire:to-account-organization-only :label="__('Select organization account')" />
|
|
<x-jetstream.input-error for="donationAccountId" class="mt-2" />
|
|
|
|
@if($donationExceedsLimit && $donationLimitError)
|
|
<div class="mt-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex items-start">
|
|
<svg class="w-5 h-5 text-red-600 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
|
</svg>
|
|
<div>
|
|
<div class="text-sm font-semibold text-red-800">{{ __('Donation exceeds account limits') }}</div>
|
|
<p class="mt-1 text-sm text-red-700">{{ $donationLimitError }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
@if(!$deleteProfileData['isCentralBank'] && !$deleteProfileData['hasNegativeBalance'] && !$deleteProfileData['isFinalAdmin'])
|
|
<div class="mt-4">
|
|
<x-jetstream.label value="{{ __('Admin password confirmation') }}" />
|
|
<x-jetstream.input type="password" class="mt-1 block w-3/4"
|
|
placeholder="{{ __('Your admin password') }}"
|
|
wire:model="adminPassword" />
|
|
<x-jetstream.input-error for="adminPassword" class="mt-2" />
|
|
</div>
|
|
@endif
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.danger-button
|
|
class="ml-3"
|
|
wire:click="deleteProfile"
|
|
wire:loading.attr="disabled"
|
|
:disabled="$deleteProfileData['hasNegativeBalance'] || $deleteProfileData['isCentralBank'] || $deleteProfileData['isFinalAdmin'] || $donationExceedsLimit">
|
|
{{ __('Delete profile') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!----Convert to Organization Profile modal ---->
|
|
@if ($modalConvertToOrganization && $convertToOrganizationData)
|
|
<x-jetstream.dialog-modal wire:model.live="modalConvertToOrganization" maxWidth="2xl">
|
|
<x-slot name="title">
|
|
{{ __('Convert to organization profile?') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="my-4 p-4 bg-gray-50 rounded-lg">
|
|
<div class="font-semibold text-gray-900">{{ $convertToOrganizationData['name'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ $convertToOrganizationData['email'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ __('Accounts') }}: {{ $convertToOrganizationData['accountCount'] }}</div>
|
|
</div>
|
|
|
|
@if (!empty($convertToOrganizationData['blockingErrors']))
|
|
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="font-semibold text-red-700 mb-1">{{ __('Cannot convert — blocking issues:') }}</div>
|
|
@foreach ($convertToOrganizationData['blockingErrors'] as $error)
|
|
<div class="text-sm text-red-600">• {{ $error }}</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (!empty($convertToOrganizationData['warnings']))
|
|
<div class="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<div class="font-semibold text-yellow-700 mb-1">{{ __('Warnings:') }}</div>
|
|
@foreach ($convertToOrganizationData['warnings'] as $warning)
|
|
<div class="text-sm text-yellow-700">• {{ $warning }}</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (empty($convertToOrganizationData['blockingErrors']))
|
|
{{-- Manager selection --}}
|
|
<div class="mb-4">
|
|
<div class="text-sm font-medium text-gray-700 mb-2">{{ __('Select organization manager') }} <span class="text-red-500">*</span></div>
|
|
<livewire:profile.select-profile
|
|
:typesAvailable="[\App\Models\User::class]"
|
|
eventName="selectedProfileForConvertToOrganization"
|
|
wire:key="select-profile-convert-to-org-{{ $convertToOrganizationData['id'] }}" />
|
|
</div>
|
|
|
|
<div class="text-sm text-gray-600">
|
|
{{ __('This will permanently convert the user to an organization profile. All associated data, accounts, posts and relationships will be migrated. This action cannot be undone.') }}
|
|
</div>
|
|
<x-errors />
|
|
@endif
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
@if (empty($convertToOrganizationData['blockingErrors']))
|
|
<x-jetstream.danger-button
|
|
class="ml-3"
|
|
wire:click="convertToOrganization"
|
|
wire:loading.attr="disabled"
|
|
:disabled="empty($convertToOrganizationSelectedManager)">
|
|
{{ __('Convert to organization profile') }}
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!----Convert to User Profile modal ---->
|
|
@if ($modalConvertToUser && $convertToUserData)
|
|
<x-jetstream.dialog-modal wire:model.live="modalConvertToUser" maxWidth="2xl">
|
|
<x-slot name="title">
|
|
{{ __('Convert to user profile?') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
{{-- Profile Info --}}
|
|
<div class="my-4 p-4 bg-gray-50 rounded-lg">
|
|
<div class="font-semibold text-gray-900">{{ $convertToUserData['name'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ $convertToUserData['email'] }}</div>
|
|
<div class="text-sm text-gray-500">{{ __('Accounts') }}: {{ $convertToUserData['accountCount'] }}</div>
|
|
</div>
|
|
|
|
{{-- Blocking errors --}}
|
|
@if (!empty($convertToUserData['blockingErrors']))
|
|
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="font-semibold text-red-700 mb-1">{{ __('Cannot convert — blocking issues:') }}</div>
|
|
@foreach ($convertToUserData['blockingErrors'] as $error)
|
|
<div class="text-sm text-red-600">• {{ $error }}</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Warnings --}}
|
|
@if (!empty($convertToUserData['warnings']))
|
|
<div class="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
|
|
<div class="font-semibold text-yellow-700 mb-1">{{ __('Warnings:') }}</div>
|
|
@foreach ($convertToUserData['warnings'] as $warning)
|
|
<div class="text-sm text-yellow-700">• {{ $warning }}</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (empty($convertToUserData['blockingErrors']))
|
|
<div class="text-sm text-gray-600">
|
|
{{ __('This will permanently convert the organization to a user profile. All associated data, accounts, posts and relationships will be migrated. This action cannot be undone.') }}
|
|
</div>
|
|
<x-errors />
|
|
@endif
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="resetForm" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
@if (empty($convertToUserData['blockingErrors']))
|
|
<x-jetstream.danger-button
|
|
class="ml-3"
|
|
wire:click="convertToUser"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Convert to user profile') }}
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('scroll-to-top', event => {
|
|
window.scrollTo({
|
|
top: 0,
|
|
behavior: 'smooth'
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|
|
|
|
</div>
|