star-cloud/resources/views/admin/basic-settings/discord-notifications/index.blade.php

994 lines
78 KiB
PHP

@extends('layouts.admin')
@section('content')
<div class="space-y-4 pb-20" x-data="{
activeTab: '{{ $activeTab }}',
tabLoading: null,
showCompanyModal: false,
showMachineModal: false,
companyId: null,
companyName: '',
companyForm: {
discord_webhook_url: '',
discord_notify_enabled: false,
discord_notify_types: [],
discord_notify_locale: 'zh_TW'
},
currentMachine: {
id: null,
name: '',
serial_no: '',
company_id: null,
settings: {},
machine_model: null
},
machineForm: {
discord_webhook_url: '',
temp_alert_enabled: 'inherit',
temp_upper_limit: '',
temp_lower_limit: ''
},
copyableMachines: @js($copyableMachines),
isTesting: false,
testStatus: null,
testMessage: '',
isLoadingTable: false,
showCopyToast: false,
copiedMachineName: '',
openCompanyEdit(company) {
const settings = company.settings || {};
this.companyId = company.id;
this.companyName = company.name;
this.companyForm.discord_webhook_url = settings.discord_webhook_url || '';
this.companyForm.discord_notify_enabled = !!(settings.discord_notify_enabled);
this.companyForm.discord_notify_types = settings.discord_notify_types || ['submachine', 'status'];
this.companyForm.discord_notify_locale = settings.locale || 'zh_TW';
this.testStatus = null;
this.testMessage = '';
this.showCompanyModal = true;
},
openMachineEdit(machine) {
const settings = machine.settings || {};
this.currentMachine = machine;
this.machineForm.discord_webhook_url = settings.discord_webhook_url || '';
this.machineForm.temp_alert_enabled = settings.temp_alert_enabled || 'inherit';
this.machineForm.temp_upper_limit = settings.temp_upper_limit !== undefined && settings.temp_upper_limit !== null ? settings.temp_upper_limit : '';
this.machineForm.temp_lower_limit = settings.temp_lower_limit !== undefined && settings.temp_lower_limit !== null ? settings.temp_lower_limit : '';
this.testStatus = null;
this.testMessage = '';
this.showMachineModal = true;
},
async testWebhook(webhookUrl) {
if (!webhookUrl) {
this.testStatus = 'error';
this.testMessage = '{{ __('Please enter Webhook URL first.') }}';
return;
}
this.isTesting = true;
this.testStatus = null;
try {
const response = await fetch('{{ route('admin.basic-settings.discord-notifications.test') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json'
},
body: JSON.stringify({ discord_webhook_url: webhookUrl })
});
const data = await response.json();
if (response.ok && data.success) {
this.testStatus = 'success';
this.testMessage = data.message;
} else {
this.testStatus = 'error';
this.testMessage = data.message || '{{ __('Failed to send test notification.') }}';
}
} catch (error) {
this.testStatus = 'error';
this.testMessage = '{{ __('An error occurred during testing.') }}';
} finally {
this.isTesting = false;
}
},
async fetchTabContent(url, containerId) {
if (!url || this.isLoadingTable) return;
this.isLoadingTable = true;
try {
const res = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
if (!res.ok) throw new Error('Network error');
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const newContent = doc.getElementById(containerId);
const container = document.getElementById(containerId);
if (newContent && container) {
container.innerHTML = newContent.innerHTML;
window.history.pushState({}, '', url);
if (window.HSStaticMethods?.autoInit) window.HSStaticMethods.autoInit();
if (window.Alpine?.initTree) window.Alpine.initTree(container);
}
} catch (e) {
console.error('AJAX Load Failed:', e);
window.location.href = url;
} finally {
this.isLoadingTable = false;
}
}
}">
<!-- Header -->
<x-page-header
:title="__('Discord Notifications')"
:subtitle="__('Configure Discord Webhook alerts for hardware exceptions and connectivity logs')"
/>
<!-- Tab Selection -->
<div class="mb-6 relative z-20">
<x-tab-nav model="activeTab">
<x-tab-nav-item value="company" :label="__('Company Settings')" />
<x-tab-nav-item value="machine" :label="__('Machine Settings')" />
</x-tab-nav>
</div>
<!-- Main Container -->
<div id="ajax-discord-container" class="relative">
<x-luxury-spinner show="isLoadingTable" />
<div :class="{ 'opacity-30 pointer-events-none': isLoadingTable }"
@ajax:navigate:discord.prevent="fetchTabContent($event.detail.url, 'ajax-discord-container')">
<!-- ================= Tab 1: 公司全域設定 ================= -->
<div x-show="activeTab === 'company'" class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden" x-cloak>
<!-- 霓虹光斑裝飾 -->
<div class="absolute -top-40 -right-40 w-80 h-80 bg-cyan-500/5 rounded-full blur-[100px] pointer-events-none"></div>
@if(auth()->user()->isSystemAdmin())
<form action="{{ route('admin.basic-settings.discord-notifications.index') }}" method="GET"
class="flex flex-col md:flex-row md:items-center gap-3 mb-8 relative z-10"
@submit.prevent="fetchTabContent($el.action + '?' + new URLSearchParams(new FormData($el)).toString() + '&tab=company', 'ajax-discord-container')">
<input type="hidden" name="tab" value="company">
<div class="relative group flex-1 md:flex-none">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</span>
<input type="text" name="search_company" value="{{ request('search_company') }}"
class="py-2.5 pl-12 pr-6 block w-full md:w-80 luxury-input"
placeholder="{{ __('Search company name or code...') }}">
</div>
<div class="flex items-center gap-2 shrink-0">
<button type="submit" class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all" title="{{ __('Search') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</button>
<button type="button"
@click="$el.closest('form').querySelector('input[name=search_company]').value=''; $el.closest('form').dispatchEvent(new Event('submit'));"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all" title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
</button>
</div>
</form>
@endif
{{-- 桌面版表格 --}}
<div class="hidden xl:block overflow-x-auto {{ !auth()->user()->isSystemAdmin() ? 'mt-6' : '' }}">
<table class="w-full text-left border-separate border-spacing-y-0">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Company Info') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Webhook URL') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Push Status') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Configured Alert Types') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
@forelse($companies as $company)
@php
$settings = $company->settings ?? [];
$isEnabled = (bool)($settings['discord_notify_enabled'] ?? false);
$webhook = $settings['discord_webhook_url'] ?? '';
$types = $settings['discord_notify_types'] ?? [];
@endphp
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
<td class="px-6 py-6">
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
</div>
<div class="min-w-0">
<p class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors tracking-tight truncate">{{ $company->name }}</p>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ $company->code }}</p>
</div>
</div>
</td>
<td class="px-6 py-6 max-w-xs">
@if($webhook)
<span class="text-xs font-mono font-bold text-slate-500 dark:text-slate-400 tracking-tight block truncate" title="{{ $webhook }}">{{ Str::limit($webhook, 40) }}</span>
@else
<span class="text-xs font-bold text-slate-300 dark:text-slate-600">{{ __('Not Configured') }}</span>
@endif
</td>
<td class="px-6 py-6 text-center">
<x-status-badge :status="$isEnabled ? 'active' : 'disabled'" size="sm" />
</td>
<td class="px-6 py-6">
<div class="flex flex-wrap gap-1.5">
@if(empty($types))
<span class="text-xs font-bold text-slate-300 dark:text-slate-600"></span>
@endif
@foreach($types as $type)
@php
$label = match($type) {
'submachine' => __('Device Exception'),
'status' => __('Connectivity Logs'),
'temperature' => __('Temperature Alert'),
default => $type
};
@endphp
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-black uppercase tracking-widest bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20">
{{ $label }}
</span>
@endforeach
</div>
</td>
<td class="px-6 py-6 text-right">
<button type="button" @click="openCompanyEdit(@js($company))"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20 tooltip" title="{{ __('Edit Settings') }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
</button>
</td>
</tr>
@empty
<x-empty-state mode="table" :colspan="5" :message="__('No records found')" />
@endforelse
</tbody>
</table>
</div>
{{-- 行動版/平版 Card 列表 --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6 {{ !auth()->user()->isSystemAdmin() ? 'mt-6' : '' }}">
@forelse($companies as $company)
@php
$settings = $company->settings ?? [];
$isEnabled = (bool)($settings['discord_notify_enabled'] ?? false);
$webhook = $settings['discord_webhook_url'] ?? '';
$types = $settings['discord_notify_types'] ?? [];
@endphp
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
</div>
<div class="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors tracking-tight">{{ $company->name }}</h3>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">{{ $company->code }}</p>
</div>
</div>
<x-status-badge :status="$isEnabled ? 'active' : 'disabled'" size="sm" />
</div>
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Webhook URL') }}</p>
@if($webhook)
<p class="text-xs font-mono font-bold text-slate-600 dark:text-slate-300 break-all leading-normal">{{ $webhook }}</p>
@else
<p class="text-xs font-bold text-slate-300 dark:text-slate-600">{{ __('Not Configured') }}</p>
@endif
</div>
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Configured Alert Types') }}</p>
<div class="flex flex-wrap gap-1.5 mt-1">
@if(empty($types))
<span class="text-xs font-bold text-slate-300 dark:text-slate-600"></span>
@endif
@foreach($types as $type)
@php
$label = match($type) {
'submachine' => __('Device Exception'),
'status' => __('Connectivity Logs'),
'temperature' => __('Temperature Alert'),
default => $type
};
@endphp
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-black uppercase tracking-widest bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20">
{{ $label }}
</span>
@endforeach
</div>
</div>
</div>
<div class="flex items-center gap-3">
<button type="button" @click="openCompanyEdit(@js($company))"
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
{{ __('Edit Settings') }}
</button>
</div>
</div>
@empty
<x-empty-state :message="__('No records found')" />
@endforelse
</div>
{{-- 分頁 --}}
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
{{ $companies->appends(array_merge(request()->query(), ['tab' => 'company']))->links('vendor.pagination.luxury', [
'ajax_navigate_event' => 'ajax:navigate:discord',
'per_page_param' => 'company_per_page',
]) }}
</div>
</div>
<!-- ================= Tab 2: 機台個別設定 ================= -->
<div x-show="activeTab === 'machine'" class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden" x-cloak>
<!-- 霓虹光斑裝飾 -->
<div class="absolute -top-40 -right-40 w-80 h-80 bg-cyan-500/5 rounded-full blur-[100px] pointer-events-none"></div>
<!-- 搜尋與篩選列 -->
<form action="{{ route('admin.basic-settings.discord-notifications.index') }}" method="GET"
class="flex flex-col md:flex-row md:items-center gap-3 mb-8 relative z-10"
@submit.prevent="fetchTabContent($el.action + '?' + new URLSearchParams(new FormData($el)).toString() + '&tab=machine', 'ajax-discord-container')">
<input type="hidden" name="tab" value="machine">
<!-- 關鍵字 -->
<div class="relative group flex-1 md:flex-none">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</span>
<input type="text" name="search_machine" value="{{ request('search_machine') }}"
class="py-2.5 pl-12 pr-6 block w-full md:w-80 luxury-input"
placeholder="{{ __('Search machine name or SN...') }}">
</div>
<!-- 公司篩選下拉選單 ( System Admin 可見) -->
@if(auth()->user()->isSystemAdmin())
<div class="w-full md:w-64 relative focus-within:z-[20]">
<select name="filter_company_id" class="luxury-input py-2.5 px-4 block w-full text-sm">
<option value="">{{ __('All Companies') }}</option>
@foreach($allCompanies as $c)
<option value="{{ $c->id }}" {{ request('filter_company_id') == $c->id ? 'selected' : '' }}>{{ $c->name }}</option>
@endforeach
</select>
</div>
@endif
<div class="flex items-center gap-2 shrink-0">
<button type="submit" class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all" title="{{ __('Search') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
</button>
<button type="button"
@click="$el.closest('form').querySelector('input[name=search_machine]').value=''; @if(auth()->user()->isSystemAdmin()) $el.closest('form').querySelector('select[name=filter_company_id]').value=''; @endif $el.closest('form').dispatchEvent(new Event('submit'));"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all" title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
</button>
</div>
</form>
{{-- 桌面版表格 --}}
<div class="hidden xl:block overflow-x-auto">
<table class="w-full text-left border-separate border-spacing-y-0">
<thead>
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Machine Info') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Model') }}</th>
@if(auth()->user()->isSystemAdmin())
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Company') }}</th>
@endif
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Webhook Status') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Temp Alert State') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
@forelse($machines as $machine)
@php
$mSettings = $machine->settings ?? [];
$mWebhook = $mSettings['discord_webhook_url'] ?? null;
$cSettings = $machine->company->settings ?? [];
$cWebhook = $cSettings['discord_webhook_url'] ?? null;
$mTempAlert = $mSettings['temp_alert_enabled'] ?? 'inherit';
// 繼承鏈判定
$mUpper = $mSettings['temp_upper_limit'] ?? null;
$mLower = $mSettings['temp_lower_limit'] ?? null;
$modelSettings = $machine->machineModel->settings ?? [];
$modelUpper = $modelSettings['temp_upper_limit'] ?? null;
$modelLower = $modelSettings['temp_lower_limit'] ?? null;
$sysUpper = 40.0;
$sysLower = 0.0;
$finalUpper = $sysUpper;
$finalLower = $sysLower;
$tempSource = __('System Default');
if ($mTempAlert === 'enabled' && ($mUpper !== null || $mLower !== null)) {
$finalUpper = $mUpper !== null ? $mUpper : $sysUpper;
$finalLower = $mLower !== null ? $mLower : $sysLower;
$tempSource = __('Custom');
} elseif ($modelUpper !== null || $modelLower !== null) {
$finalUpper = $modelUpper !== null ? $modelUpper : $sysUpper;
$finalLower = $modelLower !== null ? $modelLower : $sysLower;
$tempSource = __('Model Default');
}
@endphp
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
<td class="px-6 py-6">
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
</div>
<div class="min-w-0">
<p class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors tracking-tight truncate">{{ $machine->name }}</p>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ $machine->serial_no }}</p>
</div>
</div>
</td>
<td class="px-6 py-6">
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">
{{ $machine->machineModel->name ?? __('No Model') }}
</span>
</td>
@if(auth()->user()->isSystemAdmin())
<td class="px-6 py-6">
<span class="text-sm font-bold text-slate-600 dark:text-slate-400">
{{ $machine->company->name ?? __('No Company') }}
</span>
</td>
@endif
<td class="px-6 py-6 max-w-xs">
@if($mWebhook)
<span class="text-xs font-mono font-bold text-slate-855 dark:text-slate-200 block truncate" title="{{ $mWebhook }}">{{ Str::limit($mWebhook, 30) }}</span>
<span class="px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20 mt-1 inline-block">{{ __('Machine Specific') }}</span>
@elseif($cWebhook)
<span class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 block truncate" title="{{ $cWebhook }}">{{ Str::limit($cWebhook, 30) }}</span>
<span class="px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest bg-slate-100 dark:bg-slate-800 text-slate-500 border border-slate-200 dark:border-slate-700 mt-1 inline-block">{{ __('Inherit Company') }}</span>
@else
<span class="text-xs font-bold text-slate-300 dark:text-slate-600">{{ __('Not Configured') }}</span>
@endif
</td>
<td class="px-6 py-6">
<div class="flex flex-col">
<div class="flex items-center gap-2">
@if($mTempAlert === 'enabled')
<x-status-badge status="enabled" size="sm" />
@elseif($mTempAlert === 'disabled')
<x-status-badge status="disabled" size="sm" />
@else
<span class="px-2.5 py-0.5 rounded-full text-[10px] font-black uppercase tracking-widest bg-slate-100 dark:bg-slate-800 text-slate-500 border border-slate-200/50 dark:border-slate-700/50">
{{ __('Inherit') }}
</span>
@endif
</div>
@if($mTempAlert !== 'disabled')
<div class="mt-1 flex items-center gap-1.5">
<span class="px-2 py-0.5 rounded-full text-[10px] font-mono font-black bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20">
{{ number_format($finalLower, 1) }}°C ~ {{ number_format($finalUpper, 1) }}°C
</span>
<span class="text-[9px] font-bold text-slate-400 dark:text-slate-500" title="{{ __('Source of temperature limit settings') }}">
({{ $tempSource }})
</span>
</div>
@endif
</div>
</td>
<td class="px-6 py-6 text-right">
<button type="button" @click="openMachineEdit(@js($machine))"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20 tooltip" title="{{ __('Edit Settings') }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
</button>
</td>
</tr>
@empty
<x-empty-state mode="table" :colspan="6" :message="__('No records found')" />
@endforelse
</tbody>
</table>
</div>
{{-- 行動版/平版 Card 列表 --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($machines as $machine)
@php
$mSettings = $machine->settings ?? [];
$mWebhook = $mSettings['discord_webhook_url'] ?? null;
$cSettings = $machine->company->settings ?? [];
$cWebhook = $cSettings['discord_webhook_url'] ?? null;
$mTempAlert = $mSettings['temp_alert_enabled'] ?? 'inherit';
// 繼承鏈
$mUpper = $mSettings['temp_upper_limit'] ?? null;
$mLower = $mSettings['temp_lower_limit'] ?? null;
$modelSettings = $machine->machineModel->settings ?? [];
$modelUpper = $modelSettings['temp_upper_limit'] ?? null;
$modelLower = $modelSettings['temp_lower_limit'] ?? null;
$sysUpper = 40.0;
$sysLower = 0.0;
$finalUpper = $sysUpper;
$finalLower = $sysLower;
$tempSource = __('System Default');
if ($mTempAlert === 'enabled' && ($mUpper !== null || $mLower !== null)) {
$finalUpper = $mUpper !== null ? $mUpper : $sysUpper;
$finalLower = $mLower !== null ? $mLower : $sysLower;
$tempSource = __('Custom');
} elseif ($modelUpper !== null || $modelLower !== null) {
$finalUpper = $modelUpper !== null ? $modelUpper : $sysUpper;
$finalLower = $modelLower !== null ? $modelLower : $sysLower;
$tempSource = __('Model Default');
}
@endphp
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden shadow-sm shrink-0">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
</div>
<div class="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors tracking-tight">{{ $machine->name }}</h3>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">{{ $machine->serial_no }}</p>
</div>
</div>
@if($mTempAlert === 'enabled')
<x-status-badge status="enabled" size="sm" />
@elseif($mTempAlert === 'disabled')
<x-status-badge status="disabled" size="sm" />
@else
<span class="px-2 py-0.5 rounded-full text-[10px] font-black uppercase tracking-widest bg-slate-100 dark:bg-slate-800 text-slate-500 border border-slate-200 dark:border-slate-700">
{{ __('Inherit') }}
</span>
@endif
</div>
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Model') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $machine->machineModel->name ?? __('No Model') }}</p>
</div>
@if(auth()->user()->isSystemAdmin())
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Company') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">{{ $machine->company->name ?? __('No Company') }}</p>
</div>
@endif
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Webhook URL') }}</p>
@if($mWebhook)
<p class="text-xs font-mono font-bold text-slate-655 dark:text-slate-200 break-all leading-normal">{{ $mWebhook }}</p>
<span class="px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20 mt-1 inline-block">{{ __('Machine Specific') }}</span>
@elseif($cWebhook)
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 break-all leading-normal">{{ $cWebhook }}</p>
<span class="px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest bg-slate-100 dark:bg-slate-800 text-slate-500 border border-slate-200 dark:border-slate-700 mt-1 inline-block">{{ __('Inherit Company') }}</span>
@else
<p class="text-xs font-bold text-slate-300 dark:text-slate-600">{{ __('Not Configured') }}</p>
@endif
</div>
@if($mTempAlert !== 'disabled')
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Temp Alert Range') }}</p>
<div class="flex items-center gap-1.5 mt-1">
<span class="px-2 py-0.5 rounded-full text-[10px] font-mono font-black bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20">
{{ number_format($finalLower, 1) }}°C ~ {{ number_format($finalUpper, 1) }}°C
</span>
<span class="text-[9px] font-bold text-slate-400 dark:text-slate-500">
({{ $tempSource }})
</span>
</div>
</div>
@endif
</div>
<div class="flex items-center gap-3">
<button type="button" @click="openMachineEdit(@js($machine))"
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
{{ __('Edit Settings') }}
</button>
</div>
</div>
@empty
<x-empty-state :message="__('No records found')" />
@endforelse
</div>
{{-- 分頁 --}}
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
{{ $machines->appends(array_merge(request()->query(), ['tab' => 'machine']))->links('vendor.pagination.luxury', [
'ajax_navigate_event' => 'ajax:navigate:discord',
'per_page_param' => 'machine_per_page',
]) }}
</div>
</div>
</div>
</div>
<!-- ======================================================== -->
<!-- Tab 1: 公司全域設定 Modal -->
<!-- ======================================================== -->
<div x-show="showCompanyModal" class="fixed inset-0 z-[110] overflow-y-auto" style="display: none;" x-cloak>
<div class="flex items-center justify-center min-h-screen px-4 py-6 text-center sm:block sm:p-0">
<!-- Backdrop -->
<div x-show="showCompanyModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
@click="showCompanyModal = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<!-- Modal Content -->
<div x-show="showCompanyModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:scale-95"
class="relative inline-block align-bottom bg-white dark:bg-slate-900 rounded-[2.5rem] text-left shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl w-full border border-slate-100 dark:border-slate-800 z-10 overflow-hidden"
@click.stop>
<div class="absolute -top-40 -right-40 w-80 h-80 bg-cyan-500/10 rounded-full blur-[100px] pointer-events-none"></div>
<!-- Header -->
<div class="px-10 py-8 pb-4 flex items-center justify-between relative z-10">
<div>
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">
{{ __('Edit Company Alert Settings') }}
</h3>
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">
<span x-text="companyName" class="text-cyan-500 font-extrabold"></span>
</p>
</div>
<button @click="showCompanyModal = false"
class="p-2.5 rounded-full bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-slate-600 dark:hover:text-white transition-all border border-slate-100 dark:border-slate-700">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Form -->
<form :action="'{{ route('admin.basic-settings.discord-notifications.index') }}/company/' + companyId" method="POST">
@csrf
@method('PUT')
<div class="px-10 py-6 space-y-6 max-h-[65vh] overflow-y-auto relative z-10">
<!-- Switch and Webhook URL -->
<div class="p-6 bg-slate-50 dark:bg-slate-800/25 rounded-2xl border border-slate-100 dark:border-slate-800/50 space-y-6">
<div class="flex items-center justify-between pb-4 border-b border-slate-100 dark:border-slate-800/50">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 text-indigo-500 flex items-center justify-center border border-indigo-500/20 shrink-0">
<svg class="w-5 h-5 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
</div>
<div>
<h4 class="text-sm font-black text-slate-700 dark:text-slate-300">{{ __('General Settings') }}</h4>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('Enable and paste Webhook URL') }}</p>
</div>
</div>
<div>
<label class="relative inline-flex items-center cursor-pointer select-none">
<input type="hidden" name="discord_notify_enabled" value="0">
<input type="checkbox" name="discord_notify_enabled" value="1" x-model="companyForm.discord_notify_enabled" class="sr-only peer">
<div class="w-14 h-7 bg-slate-200 dark:bg-slate-800 rounded-full peer peer-focus:ring-2 peer-focus:ring-cyan-500/20 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[4px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-slate-600 peer-checked:bg-cyan-500 shadow-inner"></div>
</label>
</div>
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Discord Webhook URL') }}</label>
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative flex-1">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
<svg class="h-5 w-5 text-slate-400 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" /></svg>
</span>
<input type="url" name="discord_webhook_url" x-model="companyForm.discord_webhook_url" placeholder="https://discord.com/api/webhooks/..." class="luxury-input py-3 pl-12 pr-6 block w-full text-sm">
</div>
<button type="button" @click="testWebhook(companyForm.discord_webhook_url)" :disabled="isTesting" class="btn-luxury-ghost flex items-center justify-center gap-2 py-3 px-6 rounded-xl border border-slate-200 dark:border-slate-700/60 font-bold text-sm text-slate-600 dark:text-slate-300 hover:bg-cyan-500 hover:text-white active:scale-95 transition-all duration-300 shrink-0 disabled:opacity-50">
<template x-if="isTesting">
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
</template>
<template x-if="!isTesting">
<svg class="w-4 h-4 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" /></svg>
</template>
<span x-text="isTesting ? '{{ __('Sending...') }}' : '{{ __('Test Connection') }}'"></span>
</button>
</div>
<div x-show="testStatus !== null" x-transition.duration.300ms class="mt-3 p-4 rounded-xl flex items-start gap-3 text-sm font-bold animate-luxury-in" :class="testStatus === 'success' ? 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400' : 'bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400'" style="display: none;">
<template x-if="testStatus === 'success'">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</template>
<template x-if="testStatus === 'error'">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</template>
<span x-text="testMessage"></span>
</div>
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Notification Language') }}</label>
<select name="discord_notify_locale" x-model="companyForm.discord_notify_locale" class="luxury-input py-3 px-4 block w-full text-sm">
<option value="zh_TW">繁體中文 (Traditional Chinese)</option>
<option value="en">English</option>
<option value="ja">日本語 (Japanese)</option>
</select>
</div>
</div>
<!-- Alert Types -->
<div class="space-y-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-emerald-500/10 text-emerald-500 flex items-center justify-center border border-emerald-500/20 shrink-0">
<svg class="w-4 h-4 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 5.25h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5" /></svg>
</div>
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Alert Types') }}</h4>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<label class="flex items-center gap-3 p-4 rounded-xl border border-slate-100 dark:border-slate-800/40 bg-slate-50/30 dark:bg-slate-900/10 hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-all duration-300 cursor-pointer">
<input type="checkbox" name="discord_notify_types[]" value="submachine" x-model="companyForm.discord_notify_types" class="luxury-checkbox">
<span class="text-xs font-extrabold text-slate-700 dark:text-slate-200">{{ __('Submachine Alert') }}</span>
</label>
<label class="flex items-center gap-3 p-4 rounded-xl border border-slate-100 dark:border-slate-800/40 bg-slate-50/30 dark:bg-slate-900/10 hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-all duration-300 cursor-pointer">
<input type="checkbox" name="discord_notify_types[]" value="status" x-model="companyForm.discord_notify_types" class="luxury-checkbox">
<span class="text-xs font-extrabold text-slate-700 dark:text-slate-200">{{ __('Machine Connection Alert') }}</span>
</label>
<label class="flex items-center gap-3 p-4 rounded-xl border border-slate-100 dark:border-slate-800/40 bg-slate-50/30 dark:bg-slate-900/10 hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-all duration-300 cursor-pointer">
<input type="checkbox" name="discord_notify_types[]" value="temperature" x-model="companyForm.discord_notify_types" class="luxury-checkbox">
<span class="text-xs font-extrabold text-slate-700 dark:text-slate-200">{{ __('Temperature Alert') }}</span>
</label>
</div>
</div>
</div>
<!-- Footer Actions -->
<div class="px-10 py-6 border-t border-slate-100 dark:border-slate-800/50 flex justify-end gap-4 relative z-10">
<button type="button" @click="showCompanyModal = false" class="btn-luxury-ghost px-8 py-3 rounded-xl border border-slate-200 dark:border-slate-700/60 font-bold text-sm text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800">
{{ __('Cancel') }}
</button>
<button type="submit" class="btn-luxury-primary px-12 py-3 rounded-xl text-white font-bold text-sm bg-cyan-500 hover:bg-cyan-600 active:scale-95 shadow-lg shadow-cyan-500/20 transition-all duration-300">
{{ __('Save Settings') }}
</button>
</div>
</form>
</div>
</div>
</div>
<!-- ======================================================== -->
<!-- Tab 2: 機台個別設定 Modal -->
<!-- ======================================================== -->
<div x-show="showMachineModal" class="fixed inset-0 z-[110] overflow-y-auto" style="display: none;" x-cloak>
<div class="flex items-center justify-center min-h-screen px-4 py-6 text-center sm:block sm:p-0">
<!-- Backdrop -->
<div x-show="showMachineModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm transition-opacity"
@click="showMachineModal = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<!-- Modal Content -->
<div x-show="showMachineModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:scale-95"
class="relative inline-block align-bottom bg-white dark:bg-slate-900 rounded-[2.5rem] text-left shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl w-full border border-slate-100 dark:border-slate-800 z-10 overflow-hidden"
@click.stop>
<div class="absolute -top-40 -right-40 w-80 h-80 bg-cyan-500/10 rounded-full blur-[100px] pointer-events-none"></div>
<!-- Header -->
<div class="px-10 py-8 pb-4 flex items-center justify-between relative z-10">
<div>
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">
{{ __('Edit Machine Alert Settings') }}
</h3>
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-1">
<span x-text="currentMachine.name" class="text-cyan-500 font-extrabold"></span>
<span class="mx-2 text-slate-300 dark:text-slate-700">|</span>
<span x-text="currentMachine.serial_no" class="font-mono"></span>
</p>
</div>
<button @click="showMachineModal = false"
class="p-2.5 rounded-full bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-slate-600 dark:hover:text-white transition-all border border-slate-100 dark:border-slate-700">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Form -->
<form :action="'{{ route('admin.basic-settings.discord-notifications.index') }}/machine/' + currentMachine.id" method="POST">
@csrf
@method('PUT')
<div class="px-10 py-6 space-y-6 max-h-[65vh] overflow-y-auto relative z-10">
<!-- Pull-style Copy Settings (複製其他機台設定) -->
<div class="p-5 bg-gradient-to-br from-cyan-500/5 to-blue-500/5 rounded-2xl border border-cyan-500/15 relative overflow-hidden">
<div class="absolute -right-20 -bottom-20 w-40 h-40 bg-cyan-500/10 rounded-full blur-2xl"></div>
<div class="relative focus-within:z-[60] space-y-2">
<label class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-widest flex items-center gap-1.5">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376A8.965 8.965 0 0012 12.75c-.497 0-.982.04-1.455.12l-.178.03A8.965 8.965 0 006 17.25M17.25 12h-9.75m9.75 2.25h-9.75M17.25 16.5H7.5" /></svg>
{{ __('Copy Settings From Another Machine') }}
</label>
<select @change="if ($event.target.value) {
const source = copyableMachines.find(m => m.id == $event.target.value);
if (source) {
const s = source.settings || {};
machineForm.discord_webhook_url = s.discord_webhook_url || '';
machineForm.temp_alert_enabled = s.temp_alert_enabled || 'inherit';
machineForm.temp_upper_limit = s.temp_upper_limit !== undefined && s.temp_upper_limit !== null ? s.temp_upper_limit : '';
machineForm.temp_lower_limit = s.temp_lower_limit !== undefined && s.temp_lower_limit !== null ? s.temp_lower_limit : '';
copiedMachineName = source.name;
showCopyToast = true;
setTimeout(() => showCopyToast = false, 3000);
}
$event.target.value = '';
}" class="luxury-input py-2.5 px-4 block w-full text-sm">
<option value="">{{ __('Select a machine to copy settings from...') }}</option>
<template x-for="m in copyableMachines.filter(m => m.company_id === currentMachine.company_id && m.id !== currentMachine.id)" :key="m.id">
<option :value="m.id" x-text="m.name + ' (' + m.serial_no + ')'"></option>
</template>
</select>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 mt-1">
{{ __('Only machines under the same company can be cloned for security.') }}
</p>
</div>
<!-- 複製成功提示 Micro-Toast -->
<div x-show="showCopyToast"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-2 scale-95"
x-transition:enter-end="opacity-100 translate-y-0 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 translate-y-2 scale-95"
class="mt-3 p-3 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400 rounded-xl flex items-center gap-2 text-xs font-bold"
style="display: none;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
<span>{{ __('Settings loaded from') }} <span x-text="copiedMachineName" class="underline"></span>!</span>
</div>
</div>
<!-- Webhook Settings -->
<div class="p-6 bg-slate-50 dark:bg-slate-800/25 rounded-2xl border border-slate-100 dark:border-slate-800/50 space-y-4">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-indigo-500/10 text-indigo-500 flex items-center justify-center border border-indigo-500/20 shrink-0">
<svg class="w-4 h-4 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" /></svg>
</div>
<div>
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Machine-Specific Webhook') }}</h4>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 mt-0.5">{{ __('Leave empty to inherit company settings') }}</p>
</div>
</div>
</div>
<div class="space-y-2">
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative flex-1">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
<svg class="h-5 w-5 text-slate-400 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" /></svg>
</span>
<input type="url" name="discord_webhook_url" x-model="machineForm.discord_webhook_url" placeholder="https://discord.com/api/webhooks/..." class="luxury-input py-3 pl-12 pr-6 block w-full text-sm">
</div>
<button type="button" @click="testWebhook(machineForm.discord_webhook_url)" :disabled="isTesting" class="btn-luxury-ghost flex items-center justify-center gap-2 py-3 px-6 rounded-xl border border-slate-200 dark:border-slate-700/60 font-bold text-sm text-slate-600 dark:text-slate-300 hover:bg-cyan-500 hover:text-white active:scale-95 transition-all duration-300 shrink-0 disabled:opacity-50">
<template x-if="isTesting">
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
</template>
<template x-if="!isTesting">
<svg class="w-4 h-4 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" /></svg>
</template>
<span x-text="isTesting ? '{{ __('Sending...') }}' : '{{ __('Test Connection') }}'"></span>
</button>
</div>
<div x-show="testStatus !== null" x-transition.duration.300ms class="mt-3 p-4 rounded-xl flex items-start gap-3 text-sm font-bold animate-luxury-in" :class="testStatus === 'success' ? 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400' : 'bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400'" style="display: none;">
<template x-if="testStatus === 'success'">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</template>
<template x-if="testStatus === 'error'">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
</template>
<span x-text="testMessage"></span>
</div>
</div>
</div>
<!-- Temperature Overrides -->
<div class="p-6 bg-slate-50 dark:bg-slate-800/25 rounded-2xl border border-slate-100 dark:border-slate-800/50 space-y-6">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800/50">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-amber-500/10 text-amber-500 flex items-center justify-center border border-amber-500/20 shrink-0">
<svg class="w-4 h-4 stroke-[2]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v18m0-18a9 9 0 110 18m0-18a9 9 0 000 18" /></svg>
</div>
<div>
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Temperature Alert Settings') }}</h4>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 mt-0.5">{{ __('Define custom temperature limits or inherit from model') }}</p>
</div>
</div>
</div>
<!-- Alert Enable State Selection -->
<div class="space-y-2">
<label class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Alert Switch') }}</label>
<select name="temp_alert_enabled" x-model="machineForm.temp_alert_enabled" class="luxury-input py-3 px-4 block w-full text-sm">
<option value="inherit">{{ __('Inherit Model / System default') }}</option>
<option value="enabled">{{ __('Custom Enabled') }}</option>
<option value="disabled">{{ __('Custom Disabled (Mute Alert)') }}</option>
</select>
</div>
<!-- Custom Threshold Limits -->
<div x-show="machineForm.temp_alert_enabled === 'enabled'"
x-transition.duration.300ms
class="grid grid-cols-2 gap-4 animate-luxury-in">
<div class="space-y-2">
<label class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Custom Upper Limit (°C)') }}</label>
<input type="number" step="0.1" name="temp_upper_limit" x-model="machineForm.temp_upper_limit"
class="luxury-input py-2.5 px-4 block w-full text-sm"
:placeholder="currentMachine.machine_model && currentMachine.machine_model.settings && currentMachine.machine_model.settings.temp_upper_limit !== undefined ? currentMachine.machine_model.settings.temp_upper_limit : '40.0'">
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Custom Lower Limit (°C)') }}</label>
<input type="number" step="0.1" name="temp_lower_limit" x-model="machineForm.temp_lower_limit"
class="luxury-input py-2.5 px-4 block w-full text-sm"
:placeholder="currentMachine.machine_model && currentMachine.machine_model.settings && currentMachine.machine_model.settings.temp_lower_limit !== undefined ? currentMachine.machine_model.settings.temp_lower_limit : '0.0'">
</div>
</div>
</div>
</div>
<!-- Footer Actions -->
<div class="px-10 py-6 border-t border-slate-100 dark:border-slate-800/50 flex justify-end gap-4 relative z-10">
<button type="button" @click="showMachineModal = false" class="btn-luxury-ghost px-8 py-3 rounded-xl border border-slate-200 dark:border-slate-700/60 font-bold text-sm text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-800">
{{ __('Cancel') }}
</button>
<button type="submit" class="btn-luxury-primary px-12 py-3 rounded-xl text-white font-bold text-sm bg-cyan-500 hover:bg-cyan-600 active:scale-95 shadow-lg shadow-cyan-500/20 transition-all duration-300">
{{ __('Save Settings') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection