[FEAT] 優化客戶合約管理介面與修復日期偏移問題
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 52s

1. 整合「客戶詳情」與「合約歷程」至單一側邊欄,改用分頁 (Tabs) 切換介面。
2. 優化清單「服務期程」顯示:根據客戶模式(租賃/買斷)動態顯示對應期間,並使用完整文字標籤取代縮寫。
3. 修復日期 Bug:在 Company 模型指定日期序列化格式為 Y-m-d,解決時區轉換導致的日期減少一天問題。
4. 新增合約歷程資料表模型、遷移檔以及對應的多語系翻譯(中、英、日)。
5. 移除清單操作列中重複的合約歷程圖示。
This commit is contained in:
2026-04-08 17:41:26 +08:00
parent a599b14df1
commit c97776892e
9 changed files with 530 additions and 56 deletions

View File

@@ -14,7 +14,8 @@ class CompanyController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$query = Company::query()->withCount(['users', 'machines']); $query = Company::query()->withCount(['users', 'machines'])
->with(['contracts.creator:id,name']);
// 搜尋 // 搜尋
if ($search = $request->input('search')) { if ($search = $request->input('search')) {
@@ -55,6 +56,10 @@ class CompanyController extends Controller
'contact_email' => 'nullable|email|max:255', 'contact_email' => 'nullable|email|max:255',
'start_date' => 'required|date', 'start_date' => 'required|date',
'end_date' => 'nullable|date', 'end_date' => 'nullable|date',
'warranty_start_date' => 'nullable|date',
'warranty_end_date' => 'nullable|date',
'software_start_date' => 'nullable|date',
'software_end_date' => 'nullable|date',
'status' => 'required|boolean', 'status' => 'required|boolean',
'note' => 'nullable|string', 'note' => 'nullable|string',
'settings' => 'nullable|array', 'settings' => 'nullable|array',
@@ -83,11 +88,28 @@ class CompanyController extends Controller
'contact_email' => $validated['contact_email'] ?? null, 'contact_email' => $validated['contact_email'] ?? null,
'start_date' => $validated['start_date'] ?? null, 'start_date' => $validated['start_date'] ?? null,
'end_date' => $validated['end_date'] ?? null, 'end_date' => $validated['end_date'] ?? null,
'warranty_start_date' => $validated['warranty_start_date'] ?? null,
'warranty_end_date' => $validated['warranty_end_date'] ?? null,
'software_start_date' => $validated['software_start_date'] ?? null,
'software_end_date' => $validated['software_end_date'] ?? null,
'status' => $validated['status'], 'status' => $validated['status'],
'note' => $validated['note'] ?? null, 'note' => $validated['note'] ?? null,
'settings' => $validated['settings'] ?? [], 'settings' => $validated['settings'] ?? [],
]); ]);
// 記錄合約歷程
$company->contracts()->create([
'type' => $company->original_type,
'start_date' => $company->start_date,
'end_date' => $company->end_date,
'warranty_start_date' => $company->warranty_start_date,
'warranty_end_date' => $company->warranty_end_date,
'software_start_date' => $company->software_start_date,
'software_end_date' => $company->software_end_date,
'note' => __('Initial contract registration'),
'creator_id' => auth()->id(),
]);
// 如果有填寫帳號資訊,則建立管理員帳號 // 如果有填寫帳號資訊,則建立管理員帳號
if (!empty($validated['admin_username']) && !empty($validated['admin_password'])) { if (!empty($validated['admin_username']) && !empty($validated['admin_password'])) {
$user = \App\Models\System\User::create([ $user = \App\Models\System\User::create([
@@ -143,6 +165,10 @@ class CompanyController extends Controller
'contact_email' => 'nullable|email|max:255', 'contact_email' => 'nullable|email|max:255',
'start_date' => 'required|date', 'start_date' => 'required|date',
'end_date' => 'nullable|date', 'end_date' => 'nullable|date',
'warranty_start_date' => 'nullable|date',
'warranty_end_date' => 'nullable|date',
'software_start_date' => 'nullable|date',
'software_end_date' => 'nullable|date',
'status' => 'required|boolean', 'status' => 'required|boolean',
'note' => 'nullable|string', 'note' => 'nullable|string',
'settings' => 'nullable|array', 'settings' => 'nullable|array',
@@ -154,7 +180,22 @@ class CompanyController extends Controller
$validated['settings']['enable_points'] = filter_var($validated['settings']['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN); $validated['settings']['enable_points'] = filter_var($validated['settings']['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN);
} }
$company->update($validated); DB::transaction(function () use ($validated, $company) {
$company->update($validated);
// 記錄合約歷程
$company->contracts()->create([
'type' => $company->current_type,
'start_date' => $company->start_date,
'end_date' => $company->end_date,
'warranty_start_date' => $company->warranty_start_date,
'warranty_end_date' => $company->warranty_end_date,
'software_start_date' => $company->software_start_date,
'software_end_date' => $company->software_end_date,
'note' => $validated['note'] ?? __('Contract information updated'),
'creator_id' => auth()->id(),
]);
});
// 分支邏輯:若停用客戶,連帶停用其所有帳號 // 分支邏輯:若停用客戶,連帶停用其所有帳號
if ($validated['status'] == 0) { if ($validated['status'] == 0) {

View File

@@ -25,17 +25,33 @@ class Company extends Model
'status', 'status',
'start_date', 'start_date',
'end_date', 'end_date',
'warranty_start_date',
'warranty_end_date',
'software_start_date',
'software_end_date',
'note', 'note',
'settings', 'settings',
]; ];
protected $casts = [ protected $casts = [
'start_date' => 'date', 'start_date' => 'date:Y-m-d',
'end_date' => 'date', 'end_date' => 'date:Y-m-d',
'warranty_start_date' => 'date:Y-m-d',
'warranty_end_date' => 'date:Y-m-d',
'software_start_date' => 'date:Y-m-d',
'software_end_date' => 'date:Y-m-d',
'status' => 'integer', 'status' => 'integer',
'settings' => 'array', 'settings' => 'array',
]; ];
/**
* Get the contract history for the company.
*/
public function contracts(): HasMany
{
return $this->hasMany(CompanyContract::class)->latest();
}
/** /**
* Get the users for the company. * Get the users for the company.
*/ */

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Models\System;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CompanyContract extends Model
{
use HasFactory;
protected $fillable = [
'company_id',
'type',
'start_date',
'end_date',
'warranty_start_date',
'warranty_end_date',
'software_start_date',
'software_end_date',
'note',
'creator_id',
];
protected $casts = [
'start_date' => 'date:Y-m-d',
'end_date' => 'date:Y-m-d',
'warranty_start_date' => 'date:Y-m-d',
'warranty_end_date' => 'date:Y-m-d',
'software_start_date' => 'date:Y-m-d',
'software_end_date' => 'date:Y-m-d',
];
/**
* Get the company that owns the contract.
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
}
/**
* Get the user who created the record.
*/
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'creator_id');
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('companies', function (Blueprint $table) {
$table->date('warranty_start_date')->nullable()->after('end_date')->comment('保固起始日');
$table->date('warranty_end_date')->nullable()->after('warranty_start_date')->comment('保固結束日');
$table->date('software_start_date')->nullable()->after('warranty_end_date')->comment('軟體服務起始日');
$table->date('software_end_date')->nullable()->after('software_start_date')->comment('軟體服務結束日');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('companies', function (Blueprint $table) {
$table->dropColumn([
'warranty_start_date',
'warranty_end_date',
'software_start_date',
'software_end_date'
]);
});
}
};

View File

@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('company_contracts', function (Blueprint $table) {
$table->id();
$table->foreignId('company_id')->constrained()->onDelete('cascade');
$table->string('type')->comment('buyout, lease');
$table->date('start_date')->nullable();
$table->date('end_date')->nullable();
$table->date('warranty_start_date')->nullable();
$table->date('warranty_end_date')->nullable();
$table->date('software_start_date')->nullable();
$table->date('software_end_date')->nullable();
$table->text('note')->nullable();
$table->foreignId('creator_id')->nullable()->constrained('users');
$table->timestamps();
$table->index(['company_id', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('company_contracts');
}
};

View File

@@ -1119,5 +1119,31 @@
"Model changed to :model": "Model changed to :model", "Model changed to :model": "Model changed to :model",
"User logged in: :name": "User logged in: :name", "User logged in: :name": "User logged in: :name",
"Login failed: :account": "Login failed: :account", "Login failed: :account": "Login failed: :account",
"Unauthorized login attempt: :account": "Unauthorized login attempt: :account" "Unauthorized login attempt: :account": "Unauthorized login attempt: :account",
"Contract Model": "Contract Model",
"Warranty Service": "Warranty Service",
"Software Service": "Software Service",
"Modification History": "Modification History",
"No history records": "No history records",
"Initial contract registration": "Initial contract registration",
"Contract information updated": "Contract information updated",
"Warranty Start": "Warranty Start",
"Warranty End": "Warranty End",
"Software Start": "Software Start",
"Software End": "Software End",
"Contract History": "Contract History",
"Unlimited": "Unlimited",
"Change Note": "Change Note",
"Log Time": "Log Time",
"Service Periods": "Service Periods",
"Creator": "Creator",
"View Full History": "View Full History",
"Contract Start": "Contract Start",
"Contract End": "Contract End",
"Contract History Detail": "Contract History Detail",
"by": "by",
"Service Terms": "Service Periods",
"Contract": "Contract",
"Warranty": "Warranty",
"Software": "Software"
} }

View File

@@ -1118,5 +1118,31 @@
"Model changed to :model": "モデル変更::model", "Model changed to :model": "モデル変更::model",
"User logged in: :name": "ユーザーログイン::name", "User logged in: :name": "ユーザーログイン::name",
"Login failed: :account": "ログイン失敗::account", "Login failed: :account": "ログイン失敗::account",
"Unauthorized login attempt: :account": "不許可のログイン試行::account" "Unauthorized login attempt: :account": "不許可のログイン試行::account",
"Contract Model": "契約モデル",
"Warranty Service": "保証サービス",
"Software Service": "ソフトウェアサービス",
"Modification History": "変更履歴",
"No history records": "履歴なし",
"Initial contract registration": "初期契約登録",
"Contract information updated": "契約情報更新",
"Warranty Start": "保証開始",
"Warranty End": "保証終了",
"Software Start": "ソフト開始",
"Software End": "ソフト終了",
"Contract History": "契約履歴",
"Unlimited": "無期限",
"Change Note": "変更メモ",
"Log Time": "記録時間",
"Service Periods": "サービス期間",
"Creator": "作成者",
"View Full History": "全履歴を表示",
"Contract Start": "契約開始",
"Contract End": "契約終了",
"Contract History Detail": "契約履歴の詳細",
"by": "作成者:",
"Service Terms": "サービス期間",
"Contract": "契約",
"Warranty": "保証",
"Software": "ソフトウェア"
} }

View File

@@ -677,7 +677,7 @@
"Pending": "等待機台領取", "Pending": "等待機台領取",
"pending": "等待機台領取", "pending": "等待機台領取",
"Performance": "效能 (Performance)", "Performance": "效能 (Performance)",
"Permanent": "永久授權", "Permanent": "永久",
"Permanently Delete Account": "永久刪除帳號", "Permanently Delete Account": "永久刪除帳號",
"Permission Settings": "權限設定", "Permission Settings": "權限設定",
"Permissions": "權限", "Permissions": "權限",
@@ -1119,5 +1119,31 @@
"Model changed to :model": "型號變更::model", "Model changed to :model": "型號變更::model",
"User logged in: :name": "使用者登入::name", "User logged in: :name": "使用者登入::name",
"Login failed: :account": "登入失敗::account", "Login failed: :account": "登入失敗::account",
"Unauthorized login attempt: :account": "越權登入嘗試::account" "Unauthorized login attempt: :account": "越權登入嘗試::account",
"Contract Model": "合約模式",
"Warranty Service": "保固服務",
"Software Service": "軟體服務",
"Modification History": "異動歷程",
"No history records": "尚無歷史紀錄",
"Initial contract registration": "初始合約註冊",
"Contract information updated": "合約資訊已更新",
"Warranty Start": "保固起始",
"Warranty End": "保固結束",
"Software Start": "軟體起始",
"Software End": "軟體結束",
"Contract History": "合約歷程",
"Unlimited": "無限期",
"Change Note": "異動備註",
"Log Time": "記錄時間",
"Service Periods": "服務區間",
"Creator": "建立者",
"View Full History": "查看完整歷程",
"Contract Start": "合約起始",
"Contract End": "合約結束",
"Contract History Detail": "合約歷程詳情",
"by": "由",
"Service Terms": "服務期程",
"Contract": "合約",
"Warranty": "保固",
"Software": "軟體"
} }

View File

@@ -2,8 +2,11 @@
@section('content') @section('content')
<div class="space-y-6" x-data="{ <div class="space-y-6" x-data="{
showModal: false, showModal: false,
showHistoryModal: false,
editing: false, editing: false,
sidebarView: 'detail',
currentCompany: { currentCompany: {
id: '', id: '',
name: '', name: '',
@@ -16,6 +19,10 @@
contact_email: '', contact_email: '',
start_date: '', start_date: '',
end_date: '', end_date: '',
warranty_start_date: '',
warranty_end_date: '',
software_start_date: '',
software_end_date: '',
status: 1, status: 1,
note: '', note: '',
settings: { settings: {
@@ -28,7 +35,10 @@
this.currentCompany = { this.currentCompany = {
id: '', name: '', code: '', original_type: 'lease', current_type: 'lease', id: '', name: '', code: '', original_type: 'lease', current_type: 'lease',
tax_id: '', contact_name: '', contact_phone: '', tax_id: '', contact_name: '', contact_phone: '',
contact_email: '', start_date: '', end_date: '', status: 1, note: '', contact_email: '', start_date: '', end_date: '',
warranty_start_date: '', warranty_end_date: '',
software_start_date: '', software_end_date: '',
status: 1, note: '',
settings: { enable_material_code: false, enable_points: false } settings: { enable_material_code: false, enable_points: false }
}; };
this.showModal = true; this.showModal = true;
@@ -39,6 +49,10 @@
...company, ...company,
start_date: company.start_date ? company.start_date.substring(0, 10) : '', start_date: company.start_date ? company.start_date.substring(0, 10) : '',
end_date: company.end_date ? company.end_date.substring(0, 10) : '', end_date: company.end_date ? company.end_date.substring(0, 10) : '',
warranty_start_date: company.warranty_start_date ? company.warranty_start_date.substring(0, 10) : '',
warranty_end_date: company.warranty_end_date ? company.warranty_end_date.substring(0, 10) : '',
software_start_date: company.software_start_date ? company.software_start_date.substring(0, 10) : '',
software_end_date: company.software_end_date ? company.software_end_date.substring(0, 10) : '',
settings: { settings: {
enable_material_code: company.settings?.enable_material_code || false, enable_material_code: company.settings?.enable_material_code || false,
enable_points: company.settings?.enable_points || false enable_points: company.settings?.enable_points || false
@@ -57,9 +71,13 @@
detailCompany: { detailCompany: {
id: '', name: '', code: '', original_type: 'lease', current_type: 'lease', id: '', name: '', code: '', original_type: 'lease', current_type: 'lease',
tax_id: '', contact_name: '', contact_phone: '', contact_email: '', tax_id: '', contact_name: '', contact_phone: '', contact_email: '',
start_date: '', end_date: '', status: 1, note: '', start_date: '', end_date: '',
warranty_start_date: '', warranty_end_date: '',
software_start_date: '', software_end_date: '',
status: 1, note: '',
settings: { enable_material_code: false, enable_points: false }, settings: { enable_material_code: false, enable_points: false },
users_count: 0, machines_count: 0 users_count: 0, machines_count: 0,
contracts: []
}, },
openDetailSidebar(company) { openDetailSidebar(company) {
this.detailCompany = { this.detailCompany = {
@@ -69,15 +87,40 @@
enable_points: company.settings?.enable_points || false enable_points: company.settings?.enable_points || false
} }
}; };
this.sidebarView = 'detail';
this.showDetail = true; this.showDetail = true;
}, },
openHistorySidebar(company) {
this.detailCompany = {
...company,
settings: {
enable_material_code: company.settings?.enable_material_code || false,
enable_points: company.settings?.enable_points || false
}
};
this.sidebarView = 'history';
this.showDetail = true;
},
openFullHistory() {
this.showHistoryModal = true;
},
openHistory(company) {
this.detailCompany = {
...company,
settings: {
enable_material_code: company.settings?.enable_material_code || false,
enable_points: company.settings?.enable_points || false
}
};
this.showHistoryModal = true;
},
submitConfirmedForm() { submitConfirmedForm() {
if (this.statusToggleSource === 'list') { if (this.statusToggleSource === 'list') {
this.$refs.statusToggleForm.submit(); this.$refs.statusToggleForm.submit();
} else { } else {
this.$refs.companyForm.submit(); this.$refs.companyForm.submit();
} }
} },
}"> }">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
@@ -164,7 +207,7 @@
{{ __('Accounts / Machines') }}</th> {{ __('Accounts / Machines') }}</th>
<th <th
class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center"> class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-center">
{{ __('Contract Period') }}</th> {{ __('Service Terms') }}</th>
<th <th
class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-right"> class="px-6 py-4 text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest border-b border-slate-100 dark:border-slate-800 text-right">
{{ __('Actions') }}</th> {{ __('Actions') }}</th>
@@ -235,20 +278,53 @@
</div> </div>
</div> </div>
</td> </td>
<td class="px-6 py-6 text-center text-slate-500 dark:text-slate-400"> <td class="px-6 py-6 border-b border-slate-50 dark:border-slate-800/50">
<div class="flex flex-col items-center gap-1.5 font-mono"> <div class="flex flex-col gap-2.5 min-w-[200px] mx-auto w-fit">
<div class="flex items-center gap-2 min-w-[130px] justify-center"> <!-- Contract Period (Only for Lease) -->
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">{{ __('From:') }}</span> @if($company->current_type === 'lease')
<span class="text-[13px] font-bold tracking-tighter text-slate-600 dark:text-slate-300"> <div class="flex items-center gap-3 group/term">
{{ $company->start_date ? $company->start_date->format('Y-m-d') : '--' }} <span class="px-2 py-0.5 rounded-md bg-blue-500/10 text-blue-600 text-[10px] font-black border border-blue-500/20 group-hover/term:bg-blue-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
{{ __('Contract') }}
</span> </span>
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
<span class="text-slate-400">{{ $company->start_date ? $company->start_date->format('Y-m-d') : '--' }}</span>
<span class="text-slate-300">~</span>
<span class="{{ $company->end_date && $company->end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-600 dark:text-slate-300' }}">
{{ $company->end_date ? $company->end_date->format('Y-m-d') : __('Permanent') }}
</span>
</div>
</div> </div>
<div class="flex items-center gap-2 min-w-[130px] justify-center"> @endif
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">{{ __('To:') }}</span>
<span class="text-[13px] font-bold tracking-tighter {{ $company->end_date && $company->end_date->isPast() ? 'text-rose-500' : 'text-slate-800 dark:text-slate-200' }}"> @if($company->current_type === 'buyout')
{{ $company->end_date ? $company->end_date->format('Y-m-d') : __('Permanent') }} <!-- Warranty Period -->
<div class="flex items-center gap-3 group/term">
<span class="px-2 py-0.5 rounded-md bg-amber-500/10 text-amber-600 text-[10px] font-black border border-amber-500/20 group-hover/term:bg-amber-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
{{ __('Warranty') }}
</span> </span>
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
<span class="text-slate-400">{{ $company->warranty_start_date ? $company->warranty_start_date->format('Y-m-d') : '--' }}</span>
<span class="text-slate-300">~</span>
<span class="{{ $company->warranty_end_date && $company->warranty_end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-500 dark:text-slate-400' }}">
{{ $company->warranty_end_date ? $company->warranty_end_date->format('Y-m-d') : '--' }}
</span>
</div>
</div> </div>
<!-- Software Period -->
<div class="flex items-center gap-3 group/term">
<span class="px-2 py-0.5 rounded-md bg-purple-500/10 text-purple-600 text-[10px] font-black border border-purple-500/20 group-hover/term:bg-purple-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
{{ __('Software') }}
</span>
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
<span class="text-slate-400">{{ $company->software_start_date ? $company->software_start_date->format('Y-m-d') : '--' }}</span>
<span class="text-slate-300">~</span>
<span class="{{ $company->software_end_date && $company->software_end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-500 dark:text-slate-400' }}">
{{ $company->software_end_date ? $company->software_end_date->format('Y-m-d') : '--' }}
</span>
</div>
</div>
@endif
</div> </div>
</td> </td>
<td class="px-6 py-6 text-right"> <td class="px-6 py-6 text-right">
@@ -447,11 +523,11 @@
<input type="text" name="tax_id" x-model="currentCompany.tax_id" <input type="text" name="tax_id" x-model="currentCompany.tax_id"
class="luxury-input w-full"> class="luxury-input w-full">
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3" x-show="currentCompany.current_type === 'lease'">
<div class="space-y-2"> <div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ <label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{
__('Start Date') }} <span class="text-rose-500 ml-0.5">*</span></label> __('Start Date') }} <span class="text-rose-500 ml-0.5">*</span></label>
<input type="date" name="start_date" x-model="currentCompany.start_date" required <input type="date" name="start_date" x-model="currentCompany.start_date" :required="currentCompany.current_type === 'lease'"
class="luxury-input w-full px-2"> class="luxury-input w-full px-2">
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
@@ -462,6 +538,38 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Buyout Specific Dates -->
<div x-show="currentCompany.current_type === 'buyout'" x-transition class="space-y-4 pt-2">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="grid grid-cols-2 gap-3">
<div class="space-y-2">
<label class="text-[10px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty Start') }}</label>
<input type="date" name="warranty_start_date" x-model="currentCompany.warranty_start_date"
class="luxury-input w-full px-2 border-amber-100 dark:border-amber-900/30">
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty End') }}</label>
<input type="date" name="warranty_end_date" x-model="currentCompany.warranty_end_date"
class="luxury-input w-full px-2 border-amber-100 dark:border-amber-900/30">
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="space-y-2">
<label class="text-[10px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software Start') }}</label>
<input type="date" name="software_start_date" x-model="currentCompany.software_start_date"
class="luxury-input w-full px-2 border-indigo-100 dark:border-indigo-900/30">
</div>
<div class="space-y-2">
<label class="text-[10px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software End') }}</label>
<input type="date" name="software_end_date" x-model="currentCompany.software_end_date"
class="luxury-input w-full px-2 border-indigo-100 dark:border-indigo-900/30">
</div>
</div>
</div>
<!-- Keep hidden start_date for buyout as well if needed by backend -->
<input type="hidden" name="start_date" :value="currentCompany.start_date" x-if="currentCompany.current_type === 'buyout' && !currentCompany.start_date">
</div>
</div> </div>
<!-- Admin Account Section (Account Creation) - Only show when creating --> <!-- Admin Account Section (Account Creation) - Only show when creating -->
@@ -652,7 +760,7 @@
<!-- Header --> <!-- Header -->
<div class="px-8 py-6 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between bg-white dark:bg-slate-900 sticky top-0 z-10"> <div class="px-8 py-6 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between bg-white dark:bg-slate-900 sticky top-0 z-10">
<div> <div>
<h2 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('Customer Details') }}</h2> <h2 class="text-xl font-black text-slate-800 dark:text-white tracking-tight" x-text="sidebarView === 'history' ? '{{ __('Contract History Detail') }}' : '{{ __('Customer Details') }}'"></h2>
<div class="flex items-center gap-2 mt-1"> <div class="flex items-center gap-2 mt-1">
<p class="text-xs font-bold text-slate-400 uppercase tracking-[0.2em]" x-text="detailCompany.name"></p> <p class="text-xs font-bold text-slate-400 uppercase tracking-[0.2em]" x-text="detailCompany.name"></p>
<span class="text-xs font-mono font-black text-cyan-500 px-1.5 py-0.5 bg-cyan-500/10 rounded" x-text="detailCompany.code"></span> <span class="text-xs font-mono font-black text-cyan-500 px-1.5 py-0.5 bg-cyan-500/10 rounded" x-text="detailCompany.code"></span>
@@ -666,7 +774,19 @@
</div> </div>
<!-- Body --> <!-- Body -->
<div class="flex-1 overflow-y-auto px-8 py-8 space-y-8 custom-scrollbar"> <div class="flex-1 overflow-y-auto px-8 pt-4 pb-8 space-y-5 custom-scrollbar">
<!-- Tab Switcher -->
<div class="flex gap-1 p-1 bg-slate-100 dark:bg-slate-800/60 rounded-xl">
<button @click="sidebarView = 'detail'"
:class="sidebarView === 'detail' ? 'bg-white dark:bg-slate-700 text-slate-800 dark:text-white shadow-sm' : 'text-slate-400 hover:text-slate-600'"
class="flex-1 py-2.5 px-3 text-xs font-black uppercase tracking-[0.15em] rounded-lg transition-all">{{ __('Customer Details') }}</button>
<button @click="sidebarView = 'history'"
:class="sidebarView === 'history' ? 'bg-white dark:bg-slate-700 text-slate-800 dark:text-white shadow-sm' : 'text-slate-400 hover:text-slate-600'"
class="flex-1 py-2.5 px-3 text-xs font-black uppercase tracking-[0.15em] rounded-lg transition-all">{{ __('Contract History Detail') }}</button>
</div>
<!-- Detail View -->
<div x-show="sidebarView === 'detail'" class="space-y-8">
<!-- Validity & Status Section --> <!-- Validity & Status Section -->
<section class="space-y-4"> <section class="space-y-4">
<h3 class="text-xs font-black text-emerald-500 uppercase tracking-[0.3em]">{{ __('Account Status') }}</h3> <h3 class="text-xs font-black text-emerald-500 uppercase tracking-[0.3em]">{{ __('Account Status') }}</h3>
@@ -690,38 +810,56 @@
</div> </div>
<!-- Business Type --> <!-- Business Type -->
<div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80"> <div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80 space-y-4">
<h4 class="text-[10px] font-black text-indigo-500 uppercase tracking-[0.2em] mb-4">{{ __('Business Type') }}</h4> <div class="flex justify-between items-start pb-3 border-b border-slate-100/50 dark:border-slate-800/50">
<div class="space-y-4"> <div>
<div class="flex items-center justify-between"> <h4 class="text-[10px] font-black text-indigo-500 uppercase tracking-[0.2em]">{{ __('Contract Model') }}</h4>
<span class="text-[11px] font-bold text-slate-400">{{ __('Original:') }}</span>
<span class="px-2.5 py-1 rounded-lg text-[11px] font-bold uppercase tracking-widest"
:class="detailCompany.original_type === 'buyout' ? 'bg-amber-500/10 text-amber-600' : 'bg-blue-500/10 text-blue-600'"
x-text="detailCompany.original_type === 'buyout' ? '{{ __('Buyout') }}' : '{{ __('Lease') }}'"></span>
</div>
<div class="flex items-center justify-between">
<span class="text-[11px] font-bold text-slate-400">{{ __('Current:') }}</span>
<span class="px-2.5 py-1 rounded-lg text-[11px] font-bold uppercase tracking-widest"
:class="detailCompany.current_type === 'buyout' ? 'bg-amber-500/10 text-amber-600 border border-amber-500/20' : 'bg-blue-500/10 text-blue-600 border border-blue-500/20'"
x-text="detailCompany.current_type === 'buyout' ? '{{ __('Buyout') }}' : '{{ __('Lease') }}'"></span>
</div> </div>
<span :class="detailCompany.current_type === 'lease' ? 'text-blue-500 bg-blue-500/10' : 'text-amber-500 bg-amber-500/10'"
class="text-[10px] font-black uppercase tracking-wider px-2.5 py-1 rounded-lg">
<span x-text="detailCompany.current_type === 'lease' ? '{{ __('Lease') }}' : '{{ __('Buyout') }}'"></span>
</span>
</div> </div>
<!-- Lease Info Display -->
<template x-if="detailCompany.current_type === 'lease'">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-1">
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">{{ __('Start Date') }}</p>
<p class="text-xs font-black text-slate-700 dark:text-slate-200" x-text="detailCompany.start_date?.substring(0, 10) || '-'"></p>
</div>
<div class="space-y-1">
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">{{ __('End Date') }}</p>
<p class="text-xs font-black text-slate-800 dark:text-white" x-text="detailCompany.end_date?.substring(0, 10) || '{{ __('Unlimited') }}'"></p>
</div>
</div>
</template>
<!-- Buyout Info Display -->
<template x-if="detailCompany.current_type === 'buyout'">
<div class="space-y-4">
<div class="grid grid-cols-1 gap-3">
<div class="p-3 bg-amber-50/30 dark:bg-amber-500/5 rounded-xl border border-amber-100/50 dark:border-amber-500/10">
<p class="text-[9px] font-black text-amber-600 uppercase tracking-widest">{{ __('Warranty Service') }}</p>
<div class="flex items-center gap-2 mt-1">
<span class="text-xs font-black text-slate-700 dark:text-slate-200" x-text="detailCompany.warranty_start_date?.substring(0, 10) || '-'"></span>
<span class="text-slate-400">~</span>
<span class="text-xs font-black text-slate-800 dark:text-white" x-text="detailCompany.warranty_end_date?.substring(0, 10) || '-'"></span>
</div>
</div>
<div class="p-3 bg-indigo-50/30 dark:bg-indigo-500/5 rounded-xl border border-indigo-100/50 dark:border-indigo-500/10">
<p class="text-[9px] font-black text-indigo-600 uppercase tracking-widest">{{ __('Software Service') }}</p>
<div class="flex items-center gap-2 mt-1">
<span class="text-xs font-black text-slate-700 dark:text-slate-200" x-text="detailCompany.software_start_date?.substring(0, 10) || '-'"></span>
<span class="text-slate-400">~</span>
<span class="text-xs font-black text-slate-800 dark:text-white" x-text="detailCompany.software_end_date?.substring(0, 10) || '-'"></span>
</div>
</div>
</div>
</div>
</template>
</div> </div>
<!-- Contract Period -->
<div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80">
<h4 class="text-[10px] font-black text-indigo-500 uppercase tracking-[0.2em] mb-4">{{ __('Contract Period') }}</h4>
<div class="space-y-3 font-mono">
<div class="flex items-center gap-3">
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-tighter min-w-[32px]">{{ __('From:') }}</span>
<div class="text-[13px] font-bold tracking-tighter text-slate-700 dark:text-slate-200" x-text="detailCompany.start_date || '--'"></div>
</div>
<div class="flex items-center gap-3">
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-tighter min-w-[32px]">{{ __('To:') }}</span>
<div class="text-[13px] font-bold tracking-tighter text-slate-800 dark:text-white" :class="detailCompany.end_date_expired ? 'text-rose-500' : ''" x-text="detailCompany.end_date || '{{ __('Permanent') }}'"></div>
</div>
</div>
</div>
</div> </div>
</section> </section>
@@ -803,6 +941,82 @@
<p class="text-sm font-bold text-slate-600 dark:text-slate-400 leading-relaxed italic" x-text="detailCompany.note"></p> <p class="text-sm font-bold text-slate-600 dark:text-slate-400 leading-relaxed italic" x-text="detailCompany.note"></p>
</div> </div>
</section> </section>
</div>
<!-- History View -->
<div x-show="sidebarView === 'history'" class="space-y-6">
<template x-for="(contract, index) in (detailCompany.contracts || [])" :key="contract.id">
<div class="bg-slate-50/50 dark:bg-slate-800/30 rounded-2xl border border-slate-100 dark:border-slate-800/80 overflow-hidden">
<!-- Card Header -->
<div class="px-5 py-3 bg-white dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800 flex justify-between items-center">
<div class="flex items-center gap-2.5">
<div class="size-7 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center text-[9px] font-black text-slate-500" x-text="'#' + (detailCompany.contracts.length - index)"></div>
<div>
<p class="text-[10px] font-black text-slate-700 dark:text-slate-200" x-text="new Date(contract.created_at).toLocaleString()"></p>
<div class="flex items-center gap-1.5 mt-0.5">
<span :class="contract.type === 'lease' ? 'text-blue-500 bg-blue-500/10' : 'text-amber-500 bg-amber-500/10'"
class="text-[8px] font-black uppercase tracking-wider px-1.5 py-0.5 rounded"
x-text="contract.type === 'lease' ? '{{ __('Lease') }}' : '{{ __('Buyout') }}'"></span>
<span class="text-[9px] text-slate-400">{{ __('by') }} <span class="text-slate-600 dark:text-slate-300" x-text="contract.creator?.name || 'System'"></span></span>
</div>
</div>
</div>
<template x-if="index === 0">
<span class="px-2 py-0.5 bg-emerald-500 text-white text-[8px] font-black uppercase tracking-widest rounded-full">{{ __('Current') }}</span>
</template>
</div>
<!-- Card Body -->
<div class="p-5 space-y-4">
<!-- Lease dates -->
<template x-if="contract.type === 'lease'">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-1.5">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract Start') }}</p>
<p class="text-sm font-black text-slate-700 dark:text-white font-mono" x-text="contract.start_date?.substring(0, 10) || '-'"></p>
</div>
<div class="space-y-1.5">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract End') }}</p>
<p class="text-sm font-black text-slate-700 dark:text-white font-mono" x-text="contract.end_date?.substring(0, 10) || '{{ __('Unlimited') }}'"></p>
</div>
</div>
</template>
<!-- Buyout dates -->
<template x-if="contract.type === 'buyout'">
<div class="grid grid-cols-1 gap-4">
<div class="p-4 bg-amber-50/40 dark:bg-amber-500/5 rounded-2xl border border-amber-100/50 dark:border-amber-500/10">
<p class="text-[10px] font-black text-amber-600 uppercase tracking-widest mb-2">{{ __('Warranty Service') }}</p>
<div class="flex items-center gap-2">
<span class="text-sm font-black text-slate-700 dark:text-slate-200 font-mono" x-text="contract.warranty_start_date?.substring(0, 10) || '-'"></span>
<span class="text-slate-400 font-bold">~</span>
<span class="text-sm font-black text-slate-800 dark:text-white font-mono" x-text="contract.warranty_end_date?.substring(0, 10) || '-'"></span>
</div>
</div>
<div class="p-4 bg-indigo-50/40 dark:bg-indigo-500/5 rounded-2xl border border-indigo-100/50 dark:border-indigo-500/10">
<p class="text-[10px] font-black text-indigo-600 uppercase tracking-widest mb-2">{{ __('Software Service') }}</p>
<div class="flex items-center gap-2">
<span class="text-sm font-black text-slate-700 dark:text-slate-200 font-mono" x-text="contract.software_start_date?.substring(0, 10) || '-'"></span>
<span class="text-slate-400 font-bold">~</span>
<span class="text-sm font-black text-slate-800 dark:text-white font-mono" x-text="contract.software_end_date?.substring(0, 10) || '-'"></span>
</div>
</div>
</div>
</template>
<!-- Note -->
<div class="pt-2 border-t border-slate-100/50 dark:border-slate-800/50" x-show="contract.note">
<p class="text-[9px] font-bold text-slate-400 italic" x-text="contract.note"></p>
</div>
</div>
</div>
</template>
<div x-show="!(detailCompany.contracts || []).length" class="py-12 text-center">
<div class="size-14 rounded-2xl bg-slate-50 dark:bg-slate-800/50 flex items-center justify-center mx-auto mb-3 border border-slate-100 dark:border-slate-800">
<svg class="size-7 text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18c-2.305 0-4.408.867-6 2.292m0-14.25v14.25" /></svg>
</div>
<p class="text-xs font-black text-slate-400 uppercase tracking-[0.2em]">{{ __('No history records') }}</p>
</div>
</div>
</div> </div>
<!-- Footer --> <!-- Footer -->
@@ -815,7 +1029,7 @@
</div> </div>
</div> </div>
</div> </div>
</template>
</div> </div>
<style> <style>