Files
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

404 lines
30 KiB
PHP

<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
<div>
<!-- Display search results here -->
<section class="">
<div class="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
@foreach ($results as $index => $result)
<!---- PROFILE CARD --->
@if (class_basename($result['model']) === 'User' ||
class_basename($result['model']) === 'Organization' ||
class_basename($result['model']) === 'Bank')
<div wire:key="profile-{{ $result['model'] }}-{{ $result['id'] }}-{{ $index }}"
class="relative m-2 flex h-[430px] flex-col rounded-lg bg-theme-background shadow-xl sm:m-0 md:h-[550px] lg:h-[430px]">
<div class="flex-grow cursor-pointer px-6 pt-10 lg:px-14 lg:pt-14"
wire:click="showProfile('{{ $result['id'] }}', '{{ class_basename($result['model']) }}')">
<div class="flex h-full flex-col">
<div class="mb-4 flex justify-between gap-6">
<!-- Profile photo -->
<img class="flex-shrink-0 aspect-square rounded-full object-cover outline outline-1 outline-offset-1 outline-theme-secondary h-16 w-16"
src="{{ Storage::url($result['photo']) }}" />
<div class="flex-grow text-theme-primary">
<div class="flex items-start justify-between gap-4">
<!-- Profile title and subtitle - grows to take maximum space -->
<div class="flex-grow">
<h3 class="break-all text-lg font-semibold leading-tight">
{{ $result['name'] }}
</h3>
@if (class_basename($result['model']) === 'Organization')
<div class="text-xs font-normal">
<span class="bg-theme-brand px-1 text-gray-100">
{{ __('Organization') }}
</span>
</div>
@elseif (class_basename($result['model']) === 'Bank')
<div class="text-xs font-normal">
<span class="bg-theme-brand px-1 text-gray-100">
{{ __('Bank') }}
</span>
</div>
@endif
</div>
<!-- Location - aligned to right on 2xl+ screens -->
@if (!empty($result['location']))
<div class="hidden max-w-[120px] flex-shrink-0 hyphens-auto break-words text-right text-xs leading-tight text-theme-primary 2xl:block"
title="{{ $result['location'] }}">
<svg aria-label="location pin icon"
class="mr-1 inline h-3 w-3 fill-current" fill="none"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd"
d="M16.2721 10.2721C16.2721 12.4813 14.4813 14.2721 12.2721 14.2721C10.063 14.2721 8.27214 12.4813 8.27214 10.2721C8.27214 8.063 10.063 6.27214 12.2721 6.27214C14.4813 6.27214 16.2721 8.063 16.2721 10.2721ZM14.2721 10.2721C14.2721 11.3767 13.3767 12.2721 12.2721 12.2721C11.1676 12.2721 10.2721 11.3767 10.2721 10.2721C10.2721 9.16757 11.1676 8.27214 12.2721 8.27214C13.3767 8.27214 14.2721 9.16757 14.2721 10.2721Z"
fill-rule="evenodd" />
<path clip-rule="evenodd"
d="M5.79417 16.5183C2.19424 13.0909 2.05438 7.3941 5.48178 3.79418C8.90918 0.194258 14.6059 0.0543983 18.2059 3.48179C21.8058 6.90919 21.9457 12.606 18.5183 16.2059L12.3124 22.7241L5.79417 16.5183ZM17.0698 14.8268L12.243 19.8965L7.17324 15.0698C4.3733 12.404 4.26452 7.9732 6.93028 5.17326C9.59603 2.37332 14.0268 2.26454 16.8268 4.93029C19.6267 7.59604 19.7355 12.0269 17.0698 14.8268Z"
fill-rule="evenodd" />
</svg>{{ $result['location_short'] }}
</div>
@endif
<!-- Reaction buttons container -->
<div class="flex items-start gap-0 lg:gap-2 -m-2">
<!-- Star button -->
<div class="-my-3">
@livewire('reaction-button', [
'typeName' => 'star',
'showCounter' => true,
'reactionCounter' => $result['star_count'],
'modelClass' => $result['model'],
'modelId' => $result['id'],
'size' => 'w-6 h-6',
], key('star-' . $result['model'] . '-' . $result['id'] . '-' . $index))
</div>
<!-- Bookmark button - push to right on smaller screens -->
<div class="-my-3 ml-auto lg:ml-0">
@livewire('reaction-button', [
'typeName' => 'bookmark',
'showCounter' => false,
'reactionCounter' => null,
'modelClass' => $result['model'],
'modelId' => $result['id'],
'size' => 'w-6 h-6',
], key('bookmark-' . $result['model'] . '-' . $result['id'] . '-' . $index))
</div>
</div>
</div>
<!-- Full name -->
<div class="my-2 break-all text-xs font-normal">
{{ $result['full_name'] }}
</div>
<!-- Location - below name on xl and smaller screens -->
@if (!empty($result['location']))
<div class="-mt-1 text-xs leading-tight text-theme-primary 2xl:hidden"
title="{{ $result['location'] }}">
<svg aria-label="location pin icon"
class="mr-1 inline h-3 w-3 fill-current" fill="none"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd"
d="M16.2721 10.2721C16.2721 12.4813 14.4813 14.2721 12.2721 14.2721C10.063 14.2721 8.27214 12.4813 8.27214 10.2721C8.27214 8.063 10.063 6.27214 12.2721 6.27214C14.4813 6.27214 16.2721 8.063 16.2721 10.2721ZM14.2721 10.2721C14.2721 11.3767 13.3767 12.2721 12.2721 12.2721C11.1676 12.2721 10.2721 11.3767 10.2721 10.2721C10.2721 9.16757 11.1676 8.27214 12.2721 8.27214C13.3767 8.27214 14.2721 9.16757 14.2721 10.2721Z"
fill-rule="evenodd" />
<path clip-rule="evenodd"
d="M5.79417 16.5183C2.19424 13.0909 2.05438 7.3941 5.48178 3.79418C8.90918 0.194258 14.6059 0.0543983 18.2059 3.48179C21.8058 6.90919 21.9457 12.606 18.5183 16.2059L12.3124 22.7241L5.79417 16.5183ZM17.0698 14.8268L12.243 19.8965L7.17324 15.0698C4.3733 12.404 4.26452 7.9732 6.93028 5.17326C9.59603 2.37332 14.0268 2.26454 16.8268 4.93029C19.6267 7.59604 19.7355 12.0269 17.0698 14.8268Z"
fill-rule="evenodd" />
</svg>{{ $result['location_short'] }}
</div>
@endif
<!-- Deleted label -->
@if (!empty($result['deleted_at']))
<div class="text-base lg:text-xs text-red-700">
{{ __('Deleted') }}
</div>
@endif
</div>
</div>
<!--- About --->
@php
if (empty($result['about']) && !empty($result['cyclos_skills']) ) {
$about = $result['cyclos_skills'];
} else {
$about = $result['about'];
}
@endphp
@if ($result['about_short'] && $about )
<div class="mt-1 mb-2 line-clamp-2 text-sm font-semibold text-theme-primary">
{{ $result['about_short'] }}
</div>
<div class="my-auto flex min-w-0 max-w-full flex-shrink items-center">
<p
class="line-clamp-3 text-sm leading-relaxed text-theme-primary md:line-clamp-3 lg:line-clamp-3 xl:line-clamp-3 2xl:line-clamp-3">
{{ $about }}
</p>
</div>
@elseif (!$result['about_short'] && $about > 100)
<div class="my-auto flex-col lex min-w-0 max-w-full flex-shrink items-center md:line-clamp-6 lg:line-clamp-6 xl:line-clamp-6 2xl:line-clamp-6">
<div class="font-semibold">
{{ strip_tags(html_entity_decode(Illuminate\Support\Str::ucfirst(rtrim(Illuminate\Support\Str::before($about, '.'), '.')))) }}
</div>
<div
class="line-clamp-3 text-sm leading-relaxed text-theme-primary">
{{ strip_tags(html_entity_decode(Illuminate\Support\Str::limit(Illuminate\Support\Str::after($about, '.')))) }}
</div>
</div>
@elseif (!$result['about_short'] && $about <= 100)
<div class="my-auto flex-col lex min-w-0 max-w-full flex-shrink items-center md:line-clamp-6 lg:line-clamp-6 xl:line-clamp-6 2xl:line-clamp-6">
<div class="font-semibold">
{{ strip_tags(html_entity_decode(Illuminate\Support\Str::ucfirst(rtrim(Illuminate\Support\Str::before($about, '.'), '.')))) }}
</div>
<div
class="line-clamp-3 text-sm leading-relaxed text-theme-primary">
{{ strip_tags(html_entity_decode(Illuminate\Support\Str::limit(Illuminate\Support\Str::after($about, '.')))) }}
</div>
<div class="font-semibold">
{{ html_entity_decode(Illuminate\Support\Str::ucfirst(($result['motivation']))) }}
</div>
</div>
@endif
<!-- Search Highlight -->
{{-- XSS SECURITY: Highlights are sanitized in MainSearchBar::sanitizeHighlights() --}}
{{-- DO NOT change {!! !!} to {{ }} - highlights contain safe HTML <span> tags for styling --}}
{{-- DO NOT bypass sanitization - user content from profiles could contain malicious code --}}
{{-- If you modify highlight handling, review MainSearchBar.php lines 1107-1180 for security notes --}}
<div class="my-2 mr-auto py-2">
@if (isset($result['highlight']) && is_array($result['highlight']))
@foreach ($result['highlight'] as $field => $highlights)
@if (is_array($highlights))
@foreach ($highlights as $highlight)
<span
class="mb-1 mr-1 inline-block flex-shrink-0 rounded-sm bg-theme-brand px-2 py-1 text-gray-200">
{!! $highlight !!}
</span>
@endforeach
@else
<span
class="mb-1 mr-1 inline-block flex-shrink-0 rounded-sm bg-theme-brand px-2 py-1 text-gray-200">
{!! $highlights !!}
</span>
@endif
@endforeach
@elseif(isset($result['highlight']) && is_string($result['highlight']))
<span class="flex-shrink-0 rounded-sm bg-theme-brand px-2 py-1 text-gray-200">
{!! $result['highlight'] !!}
</span>
@endif
</div>
</div>
</div>
<!--- Skills -->
<div class="flex max-w-full cursor-default flex-wrap gap-2 px-6 lg:px-14 py-3"
x-data="{ openSkill: null }">
@php
// Define different limits for different screen sizes
$skillsToShow = $result['skills']->sortBy('category_color');
$xsLimit = 22; // ~4 rows on xs screens
$smLimit = 34; // ~4 rows on sm screens
$mdLimit = 26; // ~3 rows on md screens
$lgLimit = 26; // ~4 rows on lg screens
$xlLimit = 36; // ~3 rows on xl+ screens
@endphp
@foreach ($skillsToShow as $i => $skill)
<span @click.away="openSkill = null"
@click="openSkill = openSkill === {{ $i }} ? null : {{ $i }}"
@mouseenter="openSkill = {{ $i }}" @mouseleave="openSkill = null"
@touchstart.prevent="openSkill = openSkill === {{ $i }} ? null : {{ $i }}"
class="bg-{{ $skill['category_color'] . '-400' }} {{ $i >= $xsLimit ? 'hidden' : '' }} {{ $i >= $smLimit ? 'sm:hidden' : ($i >= $xsLimit ? 'sm:inline-block' : '') }} {{ $i >= $mdLimit ? 'md:hidden' : ($i >= $smLimit ? 'md:inline-block' : '') }} {{ $i >= $lgLimit ? 'lg:hidden' : ($i >= $mdLimit ? 'lg:inline-block' : '') }} {{ $i >= $xlLimit ? 'xl:hidden' : ($i >= $lgLimit ? 'xl:inline-block' : '') }} relative inline-block h-6 w-8 flex-shrink-0 rounded">
<span class="bg-{{ $skill['category_color'] . '-400' }} absolute bottom-full left-1/2 z-50 mt-2 w-max -translate-x-1/2 rounded px-2 py-1 text-sm text-black shadow-lg"
x-cloak x-show="openSkill === {{ $i }}">
{{ $skill['name'] }}
</span>
</span>
@endforeach
<!-- Responsive overflow indicators -->
@if ($skillsToShow->count() > $xsLimit)
<span
class="border-1 inline-flex h-6 w-8 flex-shrink-0 items-center justify-center rounded border border-theme-primary bg-theme-background text-xs font-semibold text-theme-secondary sm:hidden">
+{{ $skillsToShow->count() - $xsLimit }}
</span>
@endif
@if ($skillsToShow->count() > $smLimit)
<span
class="border-1 hidden h-6 w-8 flex-shrink-0 items-center justify-center rounded border border-theme-primary bg-theme-background text-xs font-semibold text-theme-secondary sm:inline-flex md:hidden">
+{{ $skillsToShow->count() - $smLimit }}
</span>
@endif
@if ($skillsToShow->count() > $mdLimit)
<span
class="border-1 hidden h-6 w-8 flex-shrink-0 items-center justify-center rounded border border-theme-primary bg-theme-background text-xs font-semibold text-theme-secondary md:inline-flex lg:hidden">
+{{ $skillsToShow->count() - $mdLimit }}
</span>
@endif
@if ($skillsToShow->count() > $lgLimit)
<span
class="border-1 hidden h-6 w-8 flex-shrink-0 items-center justify-center rounded border border-theme-primary bg-theme-background text-xs font-semibold text-theme-secondary lg:inline-flex xl:hidden">
+{{ $skillsToShow->count() - $lgLimit }}
</span>
@endif
@if ($skillsToShow->count() > $xlLimit)
<span
class="border-1 hidden h-6 w-8 flex-shrink-0 items-center justify-center rounded border border-theme-primary bg-theme-background text-xs font-semibold text-theme-secondary xl:inline-flex">
+{{ $skillsToShow->count() - $xlLimit }}
</span>
@endif
</div>
<!-- Bottom section: search score -->
<div class="mt-auto cursor-pointer px-10 pb-6"
wire:click="showProfile('{{ $result['id'] }}', '{{ class_basename($result['model']) }}')">
<div class="flex flex-wrap items-end gap-4">
<div
class="text-2xs absolute bottom-5 right-5 z-20 ml-auto pt-3 text-transparent sm:text-theme-secondary">
{{ __('search score') . ': ' . round($result['score'], 1) }}
</div>
</div>
</div>
</div>
<!---- CALL CARD --->
@elseif (class_basename($result['model']) === 'Call')
<div wire:key="call-{{ $result['id'] }}-{{ $index }}">
<x-call-card
:result="$result"
:index="$index"
wire-click="showCall('{{ $result['id'] }}')"
:show-score="true"
/>
</div>
@elseif (class_basename($result['model']) === 'Post')
<div wire:key="post-{{ $result['id'] }}-{{ $index }}"
class="relative m-2 flex h-[430px] flex-col justify-end overflow-hidden rounded-lg bg-theme-background shadow-2xl sm:m-0 md:h-[550px] lg:h-[430px]">
<a class="relative m-2 flex h-[430px] cursor-pointer flex-col justify-end overflow-hidden rounded-lg bg-theme-background shadow-2xl sm:m-0 md:h-[550px] lg:h-[430px]"
href="#" wire:click.prevent="showPost('{{ $result['id'] }}')">
<!-- Photo as background -->
@if (!empty($result['photo']))
<img alt="photo" class="absolute inset-0 z-0 h-full w-full object-cover blur-[2px]"
src="{{ $result['photo'] }}" />
<!-- Optional overlay for contrast -->
<div class="absolute inset-0 z-10 bg-gradient-to-t from-black/70 via-black/50 to-black/60"></div>
@else
<!-- Fallback background when no photo -->
<div class="absolute inset-0 z-0 bg-theme-brand"></div>
@endif
<!-- All card content on top of photo -->
<div class="relative z-20 flex h-full flex-col justify-between p-8 text-white">
<div class="flex items-start gap-4">
<h2 class="text my-3 text-4xl font-semibold leading-tight">
{{ $result['title'] }}
</h2>
@php
$dateTime = $result['meeting_date_from'] ?? null;
@endphp
@if (!empty($result['meeting_date_from']))
@if ($dateTime)
<h1 class="my-3 ml-auto pl-8 text-base font-bold text-white">
<span class="text-center text-2xl font-bold uppercase">
{{ \Carbon\Carbon::parse($dateTime)->translatedFormat('d M') }}
{{ \Carbon\Carbon::parse($dateTime)->format('H:i') . ' ' . __('messages.hour_abbrevation') }}
</span>
</h1>
@endif
@endif
</div>
<div>
<h4
class="inline-block items-center rounded-sm bg-theme-brand bg-opacity-60 px-3 pb-2 pt-1 font-normal">
{{ $result['category'] }}
</h4>
</div>
<div class="mx-6 my-3 flex h-full items-center justify-center">
<p class="w-full text-base font-semibold">
{{ $result['excerpt'] }}
</p>
</div>
<!-- Bottom section: highlights and score -->
<div class="flex flex-wrap items-end gap-4">
<p class="my-auto text-base font-normal leading-loose">
{{ $result['meeting_venue'] }}
{{ is_array($result['meeting_location']) || is_object($result['meeting_location'])
? $result['meeting_location']['name_short']
: $result['meeting_location'] }}
</p>
<div class="text-2xs ml-auto pt-3 text-white drop-shadow">
{{ __('search score') . ': ' . round($result['score'], 1) }}
</div>
</div>
</div>
</a>
</div>
@endif
@endforeach
</div>
<!-- Result totals -->
<div class="mb-3 mt-4 flex items-center justify-end text-sm text-theme-secondary">
<span class="block max-w-7xl break-words text-right">
@if (count($resultRefs) == 1)
1 {{ __('result for') }} <span class="font-semibold">{{ $searchTerm }}</span>.
@elseif (count($resultRefs) > 1)
@if (count($resultRefs) >= timebank_config('main_search_bar.search.max_results'))
{!! __('Please refine your search term.') !!}
{{ __('Only the top :shown results out of :total are shown for', ['shown' => count($resultRefs), 'total' => $total]) }} <span class="font-semibold">{{ $searchTerm }}</span>.
@else
{{ count($resultRefs) }} {{ __('results for') }} <span class="font-semibold">{{ $searchTerm }}</span>.
@endif
@else
{{ __('Sorry, no results for') }} <span class="font-semibold">{{ $searchTerm }}</span>. <span class="font-normal">{{ __('Please search again.') }}</span>
@endif
</span>
</div>
<!-- Pagination -->
@if ($results->hasPages())
<div wire:key="pagination-container" class="relative my-6 flex items-center justify-between">
<!-- Left Side: perPage Dropdown -->
<div class="hidden items-center sm:flex">
<select class="w-20 rounded-md border border-theme-primary bg-theme-background px-3 py-2 text-sm text-theme-primary shadow-sm focus:border-theme-primary focus:outline-none focus:ring focus:ring-gray-500"
wire:model.live="perPage">
<option value="15">15</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span class="ml-2 text-theme-secondary">{{ __('per page') }}</span>
</div>
<!-- Right Side: Paginator -->
{{ $results->links('livewire.long-paginator') }}
</div>
@endif
</section>
</div>
</div>
@push('scripts')
<script>
document.addEventListener('scroll-to-top', event => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
</script>
@endpush
</div>