@php $cardsPerPage = $cols * $rows; $widthCm = data_get($cardSetting, 'card.width_cm'); $heightCm = data_get($cardSetting, 'card.height_cm'); if (!$widthCm) { $wpx = data_get($cardSetting, 'card.width_px'); $widthCm = $wpx ? round($wpx / 37.8, 2) : 8.6; } if (!$heightCm) { $hpx = data_get($cardSetting, 'card.height_px'); $heightCm = $hpx ? round($hpx / 37.8, 2) : 15; } $bgFilename = data_get($cardSetting, 'card.background'); $bgUrl = asset('assets/images/card/defould.png'); if ($bgFilename) { if (str_starts_with($bgFilename, 'id-card-backgrounds/') || str_starts_with($bgFilename, 'backgrounds/')) { $bgUrl = asset('storage/' . $bgFilename); } elseif (str_starts_with($bgFilename, 'http')) { $bgUrl = $bgFilename; } else { $bgUrl = asset('assets/images/card/' . $bgFilename); } } $titleStyle = data_get($cardSetting, 'title', []); $photoStyle = data_get($cardSetting, 'photo', []); $qrStyle = data_get($cardSetting, 'qr', []); $cardWidthCm = data_get($cardSetting, 'card.width_cm', null); $cardWidthPx = data_get($cardSetting, 'card.width_px'); if (!$cardWidthPx) { $cardWidthPx = $cardWidthCm ? round($cardWidthCm * 37.8) : round(8.6 * 37.8); } $qrSizeRatio = data_get($qrStyle, 'size_ratio'); $qrSize = $qrSizeRatio ? round($qrSizeRatio * $cardWidthPx) : data_get($qrStyle, 'size', 80); @endphp @php $paperSizes = [ 'A4' => [21.0, 29.7], 'A5' => [14.8, 21.0], 'Letter' => [21.59, 27.94], 'Legal' => [21.59, 35.56], 'F4' => [21.59, 33.02] ]; [$sheetW, $sheetH] = $paperSizes[$paper] ?? $paperSizes['A4']; if (strtolower($orientation ?? '') === 'landscape') { [$sheetW, $sheetH] = [$sheetH, $sheetW]; } @endphp @php $validParticipants = collect($participants ?? []) ->filter(function($p){ return optional($p)->user !== null; }) ->values(); @endphp @foreach($validParticipants->chunk($cardsPerPage) as $pageIndex => $page)
@foreach($page as $peserta) @php $userParticipant = optional($peserta)->user; @endphp @if($userParticipant)
@php $profileParticipant = optional($userParticipant->profile); $provinceParticipant = optional($profileParticipant->province)->name ?? ($profileParticipant->other_province ?? null); $regencyParticipant = optional($profileParticipant->regency)->name ?? ($profileParticipant->other_regency ?? null); $districtParticipant = optional($profileParticipant->district)->name ?? ($profileParticipant->other_district ?? null); // Determine Photo URL $photoUrl = asset('assets/images/profilefoto/default-profile.png'); $photoPath = public_path('assets/images/profilefoto/default-profile.png'); if (!empty($profileParticipant->foto) && file_exists(public_path('assets/images/profilefoto/' . $profileParticipant->foto))) { $photoUrl = asset('assets/images/profilefoto/' . $profileParticipant->foto); $photoPath = public_path('assets/images/profilefoto/' . $profileParticipant->foto); } elseif (!empty($userParticipant->profile_photo_path) && \Illuminate\Support\Facades\Storage::disk('public')->exists($userParticipant->profile_photo_path)) { $photoUrl = asset('storage/' . $userParticipant->profile_photo_path); $photoPath = storage_path('app/public/' . $userParticipant->profile_photo_path); } elseif (!empty($userParticipant->profile_photo_url)) { $photoUrl = $userParticipant->profile_photo_url; $photoPath = null; // Cannot check size locally } $imgSize = ($photoPath && file_exists($photoPath) && is_readable($photoPath)) ? @getimagesize($photoPath) : null; $imgAspectRatio = ($imgSize && isset($imgSize[0]) && isset($imgSize[1]) && $imgSize[0] > 0 && $imgSize[1] > 0) ? ($imgSize[0] / $imgSize[1]) : 1.22; @endphp
{{-- Dynamic Elements Rendering --}} @php // Normalize elements structure $elements = data_get($cardSetting, 'elements', []); if (empty($elements) && is_array($cardSetting)) { // Backward compatibility for flat structure $elements = array_filter($cardSetting, function($k) { return !in_array($k, ['width', 'height', 'bg_type', 'bg_color', 'bg_image', 'layout', 'card', 'status']); }, ARRAY_FILTER_USE_KEY); } // Auto-Deduplicate Logic // Runs on the final $elements array (whether from 'elements' key or flat structure) if (!empty($elements)) { $seen = []; $deduped = []; foreach ($elements as $k => $el) { // Generate a signature for comparison (Data Key + Approx Position) $dKey = data_get($el, 'data_key', is_string($k) ? $k : 'unknown'); if ($dKey === 'unknown') $dKey = 'txt_'.json_encode($el); // Fallback for pure custom text without key // Determine if this is a unique field that should strictly not be duplicated (e.g. name, qr, avatar) // Unless it's a custom text element (starts with txt_) or explicitly 'custom' $isUniqueField = !str_starts_with($dKey, 'txt_') && $dKey !== 'custom'; if ($isUniqueField) { // Strict deduplication for fields: Ignore position! // This fixes "Ghost Elements" where an old element and new element exist for the same field $sig = $dKey; } else { // For custom text or shapes, we trust the ID ($k) to be unique. // We do NOT want to merge custom elements even if they are in the same position. // This ensures that if a user places two texts close to each other, both appear. $sig = $k; } if (isset($seen[$sig])) { // It's a duplicate! Skip it. continue; } $seen[$sig] = true; $deduped[$k] = $el; } // If we actually filtered something, use the filtered list if (count($deduped) < count($elements)) { $elements = $deduped; } } @endphp @foreach($elements as $key => $setting) @if(data_get($setting, 'visible', false)) @php // Check if this is new Design (has 'left') or Legacy (has 'x') $isNewDesign = isset($setting['left']); $x = data_get($setting, 'x', 0); $y = data_get($setting, 'y', 0); $left = data_get($setting, 'left', 0); $top = data_get($setting, 'top', 0); $width = data_get($setting, 'width', null); $height = data_get($setting, 'height', null); $size = data_get($setting, 'size', 12); $color = data_get($setting, 'color', '#000000'); $align = data_get($setting, 'align', 'left'); $font = data_get($setting, 'font', 'DejaVu Sans'); $weight = data_get($setting, 'weight', 'normal'); $italic = data_get($setting, 'italic', 'normal'); $posStyle = ""; if ($isNewDesign) { // New Design: uses px, left/top is top-left corner $posStyle = "position:absolute; left:{$left}px; top:{$top}px;"; if ($width) $posStyle .= "width:{$width}px;"; if ($height) $posStyle .= "height:{$height}px;"; $posStyle .= "text-align:{$align};"; // Font size in px $fontSizeUnit = 'px'; } else { // Legacy: uses mm, x might be center $posStyle = "position:absolute; left:{$x}mm; top:{$y}mm;"; if ($align === 'center') { $posStyle .= "transform: translateX(-50%); text-align: center;"; } elseif ($align === 'right') { $posStyle .= "transform: translateX(-100%); text-align: right;"; } else { $posStyle .= "text-align: left;"; } // Font size in pt $fontSizeUnit = 'pt'; } @endphp @if($key === 'avatar' || $key === 'photo' || data_get($setting, 'data_key') === 'avatar' || data_get($setting, 'data_key') === 'photo') @php $shape = data_get($setting, 'shape', 'square'); // square, circle $borderRadius = ($shape === 'circle') ? '50%' : '12px'; // If new design, size might be in width/height $imgStyle = $posStyle; if (!$isNewDesign) { $imgStyle .= "width:{$size}mm; height:{$size}mm;"; } @endphp
@elseif($key === 'qr_code' || $key === 'qr' || data_get($setting, 'data_key') === 'qr_code' || data_get($setting, 'data_key') === 'qr') @php $qrStyle = $posStyle; if (!$isNewDesign) { $qrStyle .= "width:{$size}mm; height:{$size}mm;"; } @endphp
@php $qrData = $userParticipant->id ?? 0; // Use user ID or unique code // If ActivityUser has unique code, use it. if (isset($peserta->uid)) $qrData = $peserta->uid; elseif (isset($peserta->id)) $qrData = "V:{$activity->id}:{$peserta->id}"; // For QR size, if new design use width, else use size $qrSizePx = $isNewDesign ? ($width ?? 100) : ($size * 3.78); $qrSvg = \SimpleSoftwareIO\QrCode\Facades\QrCode::size(round($qrSizePx))->generate($qrData); $qrBase64 = base64_encode($qrSvg); @endphp QR
@else {{-- Text Elements --}} @php $val = '-'; $fieldType = data_get($setting, 'fieldType'); $dataKey = data_get($setting, 'data_key', $key); $staticText = data_get($setting, 'text'); if ($fieldType === 'custom' && $staticText) { $val = $staticText; } elseif ($fieldType === 'email') { $val = $userParticipant->email ?? '-'; } elseif ($fieldType === 'phone') { $val = $profileParticipant->no_hp ?? '-'; } elseif ($fieldType === 'institution') { $val = $profileParticipant->instansi ?? '-'; } elseif ($fieldType === 'province') { $val = $provinceParticipant ?? '-'; } elseif ($fieldType === 'regency') { $val = $regencyParticipant ?? '-'; } elseif ($fieldType === 'district') { $val = $districtParticipant ?? '-'; } elseif ($dataKey === 'title') { $val = str_replace(["\r\n","\n"], ' ', ($activity->name ?? 'KARTU PESERTA')); } elseif ($dataKey === 'name') { $val = $userParticipant->name ?? '-'; } elseif ($dataKey === 'status') { $isCommittee = $activity->canManageRegistration($userParticipant->id); $val = $isCommittee ? 'PANITIA' : 'PESERTA'; } elseif ($dataKey === 'role') { $val = $peserta->role ?? $peserta->position ?? 'Peserta'; } elseif ($dataKey === 'id_number') { $val = $peserta->participant_number ?? $peserta->id ?? '-'; } elseif (isset($peserta->$dataKey)) { $val = $peserta->$dataKey; } elseif (isset($userParticipant->$dataKey)) { $val = $userParticipant->$dataKey; } elseif (isset($profileParticipant->$dataKey)) { $val = $profileParticipant->$dataKey; } elseif (isset($peserta->custom_data) && is_array($peserta->custom_data) && isset($peserta->custom_data[$dataKey])) { $val = $peserta->custom_data[$dataKey]; } elseif (isset($profileParticipant->additional_data) && is_array($profileParticipant->additional_data) && isset($profileParticipant->additional_data[$dataKey])) { $val = $profileParticipant->additional_data[$dataKey]; } // Handle object values (like relationships) if (is_object($val)) { $val = $val->name ?? '-'; } @endphp
{{ $val }}
@endif @endif @endforeach
@endif @endforeach
@endforeach