229 lines
13 KiB
PHP
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>
|