Files
timebank-cc-public/resources/views/livewire/polls/manage.blade.php
T

229 lines
13 KiB
PHP

<div class="mt-12">
<!-- Action buttons -->
<div class="mb-6 flex items-center justify-between">
@can('create posts')
<x-jetstream.button wire:click="openCreateModal" class="bg-theme-brand hover:bg-opacity-80">
{{ __('New poll') }}
</x-jetstream.button>
@endcan
</div>
<!-- Search and filter -->
<div class="mb-6">
<div class="mb-4 flex flex-wrap items-center gap-3">
<div class="relative w-full sm: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 polls') . '...' }}" type="text" wire:model.live.debounce.300ms="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="$set('search', '')" title="{{ __('Clear') }}">
<x-icon mini name="backspace" solid />
</button>
@endif
</div>
<div>
<x-select :clearable="true" class="!w-60" placeholder="{{ __('Status') }}" wire:model.live="statusFilter">
<x-select.option label="{{ __('Draft') }}" value="draft" />
<x-select.option label="{{ __('Open') }}" value="open" />
<x-select.option label="{{ __('Closed') }}" value="closed" />
<x-select.option label="{{ __('Archived') }}" value="archived" />
</x-select>
</div>
</div>
</div>
<!-- Polls table -->
<div class="bg-white shadow-sm rounded-lg overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
@foreach (['id' => __('ID'), 'title' => __('Title'), 'status' => __('Status'), 'opens_at' => __('Opens'), 'closes_at' => __('Closes'), 'responses_count' => __('Votes')] as $field => $label)
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider align-middle {{ $field !== 'title' ? 'cursor-pointer' : '' }}"
@if ($field !== 'title') wire:click="sortBy('{{ $field }}')" @endif>
<div class="flex items-center space-x-1">
<span>{{ $label }}</span>
@if ($sortField === $field)
<span class="text-gray-400">@if ($sortDirection === 'asc') ↑ @else ↓ @endif</span>
@endif
</div>
</th>
@endforeach
<th class="px-3 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse ($polls as $poll)
<tr wire:key="poll-row-{{ $poll->id }}">
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-400">{{ $poll->id }}</td>
<td class="px-3 py-4 text-sm font-medium text-gray-900">{{ $this->pollTitle($poll) }}</td>
<td class="px-3 py-4 whitespace-nowrap">
@include('livewire.polls.partials.status-badge', ['poll' => $poll])
@if ($poll->archived_at)
<span class="ml-1 text-xs text-theme-secondary">{{ __('Archived') }}</span>
@endif
</td>
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">{{ $poll->opens_at?->locale($locale)->isoFormat('lll') ?? '-' }}</td>
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-500">{{ $poll->closes_at?->locale($locale)->isoFormat('lll') ?? '-' }}</td>
<td class="px-3 py-4 whitespace-nowrap text-sm text-gray-900">{{ number_format($poll->responses_count) }}</td>
<td class="px-3 py-4 whitespace-nowrap text-sm font-medium">
<x-jetstream.secondary-button title="{{ __('Edit poll') }}" wire:click="openEditModal({{ $poll->id }})"
wire:loading.attr="disabled" wire:target="openEditModal({{ $poll->id }})">
<x-icon class="h-5 w-5" name="pencil-square" />
</x-jetstream.secondary-button>
@if (Gate::allows('update polls'))
@if ($poll->archived_at)
<x-jetstream.secondary-button title="{{ __('Restore from archive') }}" wire:click="restoreFromArchive({{ $poll->id }})" class="ml-2">
<x-icon class="h-5 w-5" name="arrow-uturn-left" />
</x-jetstream.secondary-button>
@elseif ($poll->status !== 'open')
<x-jetstream.secondary-button title="{{ __('Archive poll') }}" wire:click="archivePoll({{ $poll->id }})" class="ml-2">
<x-icon class="h-5 w-5" name="archive-box" />
</x-jetstream.secondary-button>
@endif
@endif
{{-- Only drafts nobody voted in (deletePoll() checks the ledger too) --}}
@if ($poll->status === 'draft' && $poll->responses_count === 0 && Gate::allows('delete polls'))
<x-jetstream.danger-button title="{{ __('Delete poll') }}" wire:click="confirmDelete({{ $poll->id }})" class="ml-2">
<x-icon class="h-5 w-5" name="trash" />
</x-jetstream.danger-button>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-12 text-center text-gray-500">
{{ __('No polls found.') }}
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if ($polls->hasPages())
<div class="bg-white shadow-sm rounded-lg mt-4">
<div class="px-6 py-3 border-t border-gray-200">
{{ $polls->links('livewire.long-paginator') }}
</div>
</div>
@endif
<!-- Delete confirmation -->
<x-jetstream.dialog-modal wire:model.live="showDeleteModal" wire:key="showDeletePollModal" maxWidth="2xl">
<x-slot name="title">{{ __('Delete poll') }}</x-slot>
<x-slot name="content">
{{ __('Delete this draft poll and its post?') }}
@if ($deletingPollId && ($deleting = \App\Models\Poll::with('post.translations')->find($deletingPollId)))
<br><br><strong>{{ $this->pollTitle($deleting) }}</strong>
@endif
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button no-spinner wire:click="$set('showDeleteModal', false)">{{ __('Cancel') }}</x-jetstream.secondary-button>
<x-jetstream.danger-button wire:click="deletePoll" class="ml-3">{{ __('Delete poll') }}</x-jetstream.danger-button>
</x-slot>
</x-jetstream.dialog-modal>
<!-- New poll modal -->
<x-jetstream.dialog-modal wire:model.live="showCreateModal" wire:key="showCreatePollModal" maxWidth="2xl" closeButton="true">
<x-slot name="title">
{{ __('New poll') }}
</x-slot>
<x-slot name="content">
<div class="space-y-4">
<p class="text-sm text-theme-secondary">
{{ __('This creates the poll as a post in the Poll category. Next you add the questions. An image, a longer text and other languages can be added in the Post tab.') }}
</p>
<div class="sm:w-1/3">
<label class="block text-sm text-gray-700 mb-2">{{ __('Language') }}</label>
<select wire:model="newPoll.locale" class="w-full border border-primary-300 bg-theme-background rounded-md shadow-sm text-sm">
@foreach ($locales as $code)
<option value="{{ $code }}">{{ strtoupper($code) }}</option>
@endforeach
</select>
@include('polls.types._error', ['field' => 'newPoll.locale'])
</div>
<div>
<label class="block text-sm text-gray-700 mb-2">{{ __('Title') }}</label>
<x-input errorless wire:model="newPoll.title" type="text" class="w-full" maxlength="150" />
@include('polls.types._error', ['field' => 'newPoll.title'])
</div>
<div>
<label class="block text-sm text-gray-700 mb-2">{{ __('Introduction (optional)') }}</label>
<x-textarea errorless wire:model="newPoll.excerpt" rows="3" maxlength="{{ timebank_config('posts.excerpt_max_input', 500) }}" class="w-full" />
@include('polls.types._error', ['field' => 'newPoll.excerpt'])
</div>
</div>
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="$set('showCreateModal', false)">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
<x-jetstream.button wire:click="createPoll" wire:loading.attr="disabled" wire:target="createPoll" class="ml-3 bg-theme-brand">
<span wire:loading.remove wire:target="createPoll">{{ __('Create and add questions') }}</span>
<span wire:loading wire:target="createPoll">{{ __('Processing...') }}</span>
</x-jetstream.button>
</x-slot>
</x-jetstream.dialog-modal>
<!-- Edit poll modal -->
<x-jetstream.dialog-modal wire:model.live="showEditModal" wire:key="showEditPollModal" maxWidth="4xl" closeButton="true">
<x-slot name="title">
{{ __('Edit poll') }}: {{ $this->pollTitle($editingPoll) }}
</x-slot>
<x-slot name="content">
@if ($editingPoll)
<!-- Tabs -->
<div class="mb-6 border-b border-theme-border">
<nav class="-mb-px flex flex-wrap gap-x-6" role="tablist">
@php($errorPrefixes = ['post' => ['postFields', 'image', 'mediaOwner', 'mediaCaptions'], 'questions' => ['questions'], 'settings' => ['form.'], 'targeting' => ['targeting.'], 'results' => []])
@foreach (['post' => __('Post'), 'questions' => __('Questions'), 'settings' => __('Settings'), 'targeting' => __('Target audience'), 'results' => __('Results')] as $key => $label)
@php($tabHasErrors = collect($errors->keys())->contains(fn ($error) => \Illuminate\Support\Str::startsWith($error, $errorPrefixes[$key])))
<button type="button" role="tab" aria-selected="{{ $tab === $key ? 'true' : 'false' }}"
wire:click="$set('tab', '{{ $key }}')"
class="whitespace-nowrap border-b-2 py-2 text-sm {{ $tab === $key ? 'border-theme-brand text-theme-primary font-semibold' : 'border-transparent text-theme-secondary hover:text-theme-primary' }}">
{{ $label }}
@if ($tabHasErrors)
<span class="ml-1 inline-block h-2 w-2 rounded-full bg-red-600" title="{{ __('This tab has errors') }}"></span>
@endif
</button>
@endforeach
</nav>
</div>
<div @class(['hidden' => $tab !== 'post'])>
@include('livewire.polls.partials.post')
</div>
<div @class(['hidden' => $tab !== 'questions'])>
@include('livewire.polls.partials.questions')
</div>
<div @class(['hidden' => $tab !== 'settings'])>
@include('livewire.polls.partials.settings')
</div>
{{-- Kept in the DOM (hidden) so the location filter keeps its state between tabs --}}
<div @class(['hidden' => $tab !== 'targeting'])>
@include('livewire.polls.partials.targeting')
</div>
@if ($tab === 'results' && $results)
@include('livewire.polls.partials.results')
@endif
@endif
</x-slot>
<x-slot name="footer">
<x-jetstream.secondary-button wire:click="closeModal">
{{ __('Cancel') }}
</x-jetstream.secondary-button>
@can('update posts')
<x-jetstream.button wire:click="savePoll" wire:loading.attr="disabled" wire:target="savePoll" class="ml-3 bg-theme-brand">
<span wire:loading.remove wire:target="savePoll">{{ __('Save poll') }}</span>
<span wire:loading wire:target="savePoll">{{ __('Processing...') }}</span>
</x-jetstream.button>
@endcan
</x-slot>
</x-jetstream.dialog-modal>
</div>