// モーダルコンポーネント Alpine.data('auditHistoryModal', () => ({ // 詳細モーダル showDetailModal: false, selectedRecord: null, // セッションモーダル showSessionModal: false, currentSessionId: '', sessionHistory: [], loadingSessionHistory: false, // エクスポートモーダル showExportModal: false, exportStatus: 'pending', // pending, processing, completed, failed exportJobId: '', exportProgress: 0, exportRecordCount: 0, exportProcessedCount: 0, exportFileSize: 0, exportErrorMessage: '', exportPollingTimer: null, initializeModal() { // 詳細表示イベントリスナー this.$el.addEventListener('show-detail-modal', (event) => { this.openDetailModal(event.detail); }); // セッション履歴表示イベントリスナー this.$el.addEventListener('show-session-history', (event) => { this.openSessionModal(event.detail); }); // エクスポート開始イベントリスナー this.$el.addEventListener('export-started', (event) => { this.openExportModal(event.detail); }); }, // 詳細モーダル関連 async openDetailModal(record) { this.selectedRecord = null; this.showDetailModal = true; try { // 詳細データを取得 const endpoint = record.history_type === 'audit' ? `{{ route('audit-history.audit.detail', ':id') }}`.replace(':id', record.id) : `{{ route('audit-history.activity.detail', ':id') }}`.replace(':id', record.id); const response = await fetch(endpoint); if (!response.ok) { throw new Error('詳細データの取得に失敗しました。'); } const data = await response.json(); if (data.success) { this.selectedRecord = data.data; } else { throw new Error(data.message || '詳細データの取得に失敗しました。'); } } catch (error) { console.error('Detail fetch error:', error); alert('詳細データの取得中にエラーが発生しました: ' + error.message); this.closeDetailModal(); } }, closeDetailModal() { this.showDetailModal = false; this.selectedRecord = null; }, getModalTitle() { if (!this.selectedRecord) return '読み込み中...'; return this.selectedRecord.type === 'audit' ? '監査履歴詳細' : '操作履歴詳細'; }, getEventName(event) { const eventNames = { 'created': '新規作成', 'updated': '更新', 'deleted': '削除', 'restored': '復元' }; return eventNames[event] || event || '-'; }, getChangeTypeClass(changeType) { const classes = { 'created': 'bg-green-100 text-green-800', 'updated': 'bg-yellow-100 text-yellow-800', 'deleted': 'bg-red-100 text-red-800' }; return classes[changeType] || 'bg-gray-100 text-gray-800'; }, getChangeTypeName(changeType) { const names = { 'created': '新規', 'updated': '変更', 'deleted': '削除' }; return names[changeType] || changeType; }, // セッションモーダル関連 async openSessionModal(sessionId) { this.currentSessionId = sessionId; this.sessionHistory = []; this.loadingSessionHistory = true; this.showSessionModal = true; try { const response = await fetch(`{{ route('audit-history.session.history', ':sessionId') }}`.replace(':sessionId', sessionId)); if (!response.ok) { throw new Error('セッション履歴の取得に失敗しました。'); } const data = await response.json(); if (data.success) { this.sessionHistory = data.data.history || []; } else { throw new Error(data.message || 'セッション履歴の取得に失敗しました。'); } } catch (error) { console.error('Session history fetch error:', error); alert('セッション履歴の取得中にエラーが発生しました: ' + error.message); } finally { this.loadingSessionHistory = false; } }, closeSessionModal() { this.showSessionModal = false; this.currentSessionId = ''; this.sessionHistory = []; }, getSessionTimeRange() { if (this.sessionHistory.length === 0) return '-'; const dates = this.sessionHistory.map(h => new Date(h.created_at)).sort((a, b) => a - b); const start = dates[0]; const end = dates[dates.length - 1]; const formatter = new Intl.DateTimeFormat('ja-JP', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); return `${formatter.format(start)} - ${formatter.format(end)}`; }, getTimelineDescription(history) { if (history.history_type === 'audit') { const eventName = this.getEventName(history.event); const tableName = this.getTableName(history.auditable_type); return `${tableName}の${eventName}(ID: ${history.auditable_id})`; } else { return history.description || `${history.menu_name}での${history.action_type}`; } }, getTableName(modelType) { const tableNames = { 'App\\Models\\User': 'ユーザー', 'App\\Models\\Product': '商品', 'App\\Models\\Customer': '顧客', 'App\\Models\\OrderHeader': '受注ヘッダー' }; return tableNames[modelType] || (modelType ? modelType.split('\\').pop() : ''); }, showDetailFromSession(history) { // セッションモーダルから詳細モーダルを開く this.openDetailModal(history); }, exportSessionHistory() { // セッション履歴のエクスポート(今回は簡易実装) alert('セッション履歴のエクスポート機能は今後実装予定です。'); }, // エクスポートモーダル関連 openExportModal(exportData) { this.exportJobId = exportData.jobId; this.exportRecordCount = exportData.recordCount; this.exportStatus = 'pending'; this.exportProgress = 0; this.exportProcessedCount = 0; this.exportFileSize = 0; this.exportErrorMessage = ''; this.showExportModal = true; // ポーリング開始 this.startExportPolling(); }, closeExportModal() { this.showExportModal = false; if (this.exportPollingTimer) { clearInterval(this.exportPollingTimer); this.exportPollingTimer = null; } }, startExportPolling() { this.exportPollingTimer = setInterval(async () => { await this.checkExportStatus(); }, 2000); // 2秒間隔でチェック }, async checkExportStatus() { try { const response = await fetch(`{{ route('audit-history.export.status', ':jobId') }}`.replace(':jobId', this.exportJobId)); if (!response.ok) { throw new Error('エクスポート状況の確認に失敗しました。'); } const data = await response.json(); if (data.success) { this.exportStatus = data.data.status; this.exportProgress = data.data.progress || 0; this.exportProcessedCount = data.data.processed_count || 0; this.exportFileSize = data.data.file_size || 0; if (this.exportStatus === 'completed' || this.exportStatus === 'failed') { if (this.exportStatus === 'failed') { this.exportErrorMessage = data.data.error || 'エクスポート処理でエラーが発生しました。'; } clearInterval(this.exportPollingTimer); this.exportPollingTimer = null; } } } catch (error) { console.error('Export status check error:', error); this.exportStatus = 'failed'; this.exportErrorMessage = error.message; clearInterval(this.exportPollingTimer); this.exportPollingTimer = null; } }, async downloadExportFile() { try { const response = await fetch(`{{ route('audit-history.export.download', ':jobId') }}`.replace(':jobId', this.exportJobId)); if (!response.ok) { throw new Error('ファイルのダウンロードに失敗しました。'); } // ファイルダウンロード const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `audit_history_${new Date().getTime()}.xlsx`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); this.closeExportModal(); } catch (error) { console.error('Download error:', error); alert('ダウンロード中にエラーが発生しました: ' + error.message); } }, retryExport() { this.exportStatus = 'pending'; this.exportProgress = 0; this.exportProcessedCount = 0; this.exportErrorMessage = ''; this.startExportPolling(); }, formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } }));