Files
HickLab3D_Dashboard/app/Services/Display.php
T
HickmeisterandClaude Sonnet 5.5 f7e3c8fe16
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 28s
Show destination flags and readable shipment dates
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:24:22 +01:00

112 lines
6.3 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Carbon;
use Illuminate\Support\HtmlString;
use Throwable;
class Display
{
/** @var array<string, string> ISO 3166-1 alpha-3 to alpha-2 */
private const ALPHA3_TO_ALPHA2 = [
'AFG' => 'AF', 'ALA' => 'AX', 'ALB' => 'AL', 'DZA' => 'DZ', 'ASM' => 'AS', 'AND' => 'AD', 'AGO' => 'AO', 'AIA' => 'AI',
'ATG' => 'AG', 'ARG' => 'AR', 'ARM' => 'AM', 'ABW' => 'AW', 'AUS' => 'AU', 'AUT' => 'AT', 'AZE' => 'AZ', 'BHS' => 'BS',
'BHR' => 'BH', 'BGD' => 'BD', 'BRB' => 'BB', 'BLR' => 'BY', 'BEL' => 'BE', 'BLZ' => 'BZ', 'BEN' => 'BJ', 'BMU' => 'BM',
'BTN' => 'BT', 'BOL' => 'BO', 'BIH' => 'BA', 'BWA' => 'BW', 'BRA' => 'BR', 'VGB' => 'VG', 'BRN' => 'BN', 'BGR' => 'BG',
'BFA' => 'BF', 'BDI' => 'BI', 'KHM' => 'KH', 'CMR' => 'CM', 'CAN' => 'CA', 'CPV' => 'CV', 'CYM' => 'KY', 'CAF' => 'CF',
'TCD' => 'TD', 'CHL' => 'CL', 'CHN' => 'CN', 'COL' => 'CO', 'COM' => 'KM', 'COG' => 'CG', 'COD' => 'CD', 'COK' => 'CK',
'CRI' => 'CR', 'CIV' => 'CI', 'HRV' => 'HR', 'CUB' => 'CU', 'CUW' => 'CW', 'CYP' => 'CY', 'CZE' => 'CZ', 'DNK' => 'DK',
'DJI' => 'DJ', 'DMA' => 'DM', 'DOM' => 'DO', 'ECU' => 'EC', 'EGY' => 'EG', 'SLV' => 'SV', 'GNQ' => 'GQ', 'ERI' => 'ER',
'EST' => 'EE', 'SWZ' => 'SZ', 'ETH' => 'ET', 'FRO' => 'FO', 'FJI' => 'FJ', 'FIN' => 'FI', 'FRA' => 'FR', 'GUF' => 'GF',
'PYF' => 'PF', 'GAB' => 'GA', 'GMB' => 'GM', 'GEO' => 'GE', 'DEU' => 'DE', 'GHA' => 'GH', 'GIB' => 'GI', 'GRC' => 'GR',
'GRL' => 'GL', 'GRD' => 'GD', 'GLP' => 'GP', 'GUM' => 'GU', 'GTM' => 'GT', 'GGY' => 'GG', 'GIN' => 'GN', 'GNB' => 'GW',
'GUY' => 'GY', 'HTI' => 'HT', 'HND' => 'HN', 'HKG' => 'HK', 'HUN' => 'HU', 'ISL' => 'IS', 'IND' => 'IN', 'IDN' => 'ID',
'IRN' => 'IR', 'IRQ' => 'IQ', 'IRL' => 'IE', 'IMN' => 'IM', 'ISR' => 'IL', 'ITA' => 'IT', 'JAM' => 'JM', 'JPN' => 'JP',
'JEY' => 'JE', 'JOR' => 'JO', 'KAZ' => 'KZ', 'KEN' => 'KE', 'KIR' => 'KI', 'XKX' => 'XK', 'KWT' => 'KW', 'KGZ' => 'KG',
'LAO' => 'LA', 'LVA' => 'LV', 'LBN' => 'LB', 'LSO' => 'LS', 'LBR' => 'LR', 'LBY' => 'LY', 'LIE' => 'LI', 'LTU' => 'LT',
'LUX' => 'LU', 'MAC' => 'MO', 'MDG' => 'MG', 'MWI' => 'MW', 'MYS' => 'MY', 'MDV' => 'MV', 'MLI' => 'ML', 'MLT' => 'MT',
'MHL' => 'MH', 'MTQ' => 'MQ', 'MRT' => 'MR', 'MUS' => 'MU', 'MYT' => 'YT', 'MEX' => 'MX', 'FSM' => 'FM', 'MDA' => 'MD',
'MCO' => 'MC', 'MNG' => 'MN', 'MNE' => 'ME', 'MSR' => 'MS', 'MAR' => 'MA', 'MOZ' => 'MZ', 'MMR' => 'MM', 'NAM' => 'NA',
'NRU' => 'NR', 'NPL' => 'NP', 'NLD' => 'NL', 'NCL' => 'NC', 'NZL' => 'NZ', 'NIC' => 'NI', 'NER' => 'NE', 'NGA' => 'NG',
'NIU' => 'NU', 'PRK' => 'KP', 'MKD' => 'MK', 'MNP' => 'MP', 'NOR' => 'NO', 'OMN' => 'OM', 'PAK' => 'PK', 'PLW' => 'PW',
'PSE' => 'PS', 'PAN' => 'PA', 'PNG' => 'PG', 'PRY' => 'PY', 'PER' => 'PE', 'PHL' => 'PH', 'POL' => 'PL', 'PRT' => 'PT',
'PRI' => 'PR', 'QAT' => 'QA', 'REU' => 'RE', 'ROU' => 'RO', 'RUS' => 'RU', 'RWA' => 'RW', 'WSM' => 'WS', 'SMR' => 'SM',
'STP' => 'ST', 'SAU' => 'SA', 'SEN' => 'SN', 'SRB' => 'RS', 'SYC' => 'SC', 'SLE' => 'SL', 'SGP' => 'SG', 'SXM' => 'SX',
'SVK' => 'SK', 'SVN' => 'SI', 'SLB' => 'SB', 'SOM' => 'SO', 'ZAF' => 'ZA', 'KOR' => 'KR', 'SSD' => 'SS', 'ESP' => 'ES',
'LKA' => 'LK', 'KNA' => 'KN', 'LCA' => 'LC', 'VCT' => 'VC', 'SDN' => 'SD', 'SUR' => 'SR', 'SWE' => 'SE', 'CHE' => 'CH',
'SYR' => 'SY', 'TWN' => 'TW', 'TJK' => 'TJ', 'TZA' => 'TZ', 'THA' => 'TH', 'TLS' => 'TL', 'TGO' => 'TG', 'TON' => 'TO',
'TTO' => 'TT', 'TUN' => 'TN', 'TUR' => 'TR', 'TKM' => 'TM', 'TCA' => 'TC', 'TUV' => 'TV', 'UGA' => 'UG', 'UKR' => 'UA',
'ARE' => 'AE', 'GBR' => 'GB', 'USA' => 'US', 'VIR' => 'VI', 'URY' => 'UY', 'UZB' => 'UZ', 'VUT' => 'VU', 'VAT' => 'VA',
'VEN' => 'VE', 'VNM' => 'VN', 'WLF' => 'WF', 'YEM' => 'YE', 'ZMB' => 'ZM', 'ZWE' => 'ZW',
];
/** @var array<string, string> */
private const COUNTRY_ALIASES = [
'UK' => 'GB', 'UNITED KINGDOM' => 'GB', 'GREAT BRITAIN' => 'GB', 'ENGLAND' => 'GB', 'SCOTLAND' => 'GB',
'WALES' => 'GB', 'NORTHERN IRELAND' => 'GB', 'UNITED STATES' => 'US', 'IRELAND' => 'IE',
];
/**
* Resolve an ISO alpha-2 code from an address, accepting alpha-2, alpha-3 or common country names.
*
* @param array<string, mixed> $address
*/
public function countryCode(array $address): ?string
{
foreach (['CountryIsoCode', 'CountryISOCode', 'CountryIso3Code', 'CountryISO3Code', 'CountryIso', 'Country'] as $field) {
$value = strtoupper(trim((string) ($address[$field] ?? '')));
if ($value === '') {
continue;
}
if (isset(self::COUNTRY_ALIASES[$value])) {
return self::COUNTRY_ALIASES[$value];
}
if (strlen($value) === 3 && isset(self::ALPHA3_TO_ALPHA2[$value])) {
return self::ALPHA3_TO_ALPHA2[$value];
}
if (preg_match('/^[A-Z]{2}$/', $value) === 1) {
return $value;
}
}
return null;
}
/**
* Render a small flag image for the address destination, or a dash when the country is unknown.
*
* @param array<string, mixed> $address
*/
public function flag(array $address): HtmlString
{
$code = $this->countryCode($address);
if ($code === null) {
return new HtmlString('<span class="text-slate-500">—</span>');
}
$lower = strtolower($code);
return new HtmlString(sprintf(
'<span class="inline-flex items-center gap-2"><img src="https://flagcdn.com/w40/%1$s.png" srcset="https://flagcdn.com/w80/%1$s.png 2x" width="20" height="15" alt="%2$s flag" loading="lazy" class="rounded-sm"><span>%2$s</span></span>',
$lower,
e($code),
));
}
/** Format an API date or date-time as a readable string, dropping the time when it is midnight. */
public function dateTime(string|int|null $value): string
{
if ($value === null || $value === '') {
return '—';
}
try {
$date = is_int($value) ? Carbon::createFromTimestamp($value) : Carbon::parse($value);
} catch (Throwable) {
return (string) $value;
}
return $date->format($date->format('H:i:s') === '00:00:00' ? 'D j M Y' : 'D j M Y, H:i');
}
}