<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MECA - Manufacturing Execution Change Assistant</title>
<style>
/* ======================================================
3DEXPERIENCE OOTB DESIGN SYSTEM
====================================================== */
:root {
/* 3DX Brand Colors */
--3dx-blue: #0078BE;
--3dx-blue-dark: #005A8E;
--3dx-blue-light: #E6F3FA;
--3dx-blue-hover: #0068A5;
/* 3DX Gray Scale */
--3dx-gray-900: #1A1A1A;
--3dx-gray-800: #333333;
--3dx-gray-700: #4D4D4D;
--3dx-gray-600: #666666;
--3dx-gray-500: #8C8C8C;
--3dx-gray-400: #B3B3B3;
--3dx-gray-300: #D9D9D9;
--3dx-gray-200: #E6E6E6;
--3dx-gray-100: #F2F2F2;
--3dx-gray-50: #FAFAFA;
/* Semantic Colors */
--3dx-success: #1D8E4C;
--3dx-success-bg: #E8F5E9;
--3dx-warning: #F59E0B;
--3dx-warning-bg: #FFFBEB;
--3dx-error: #D32F2F;
--3dx-error-bg: #FFEBEE;
--3dx-info: #1976D2;
--3dx-info-bg: #E3F2FD;
/* 3DX Surface Colors */
--3dx-background: #F5F5F5;
--3dx-surface: #FFFFFF;
--3dx-surface-elevated: #FFFFFF;
/* Borders */
--3dx-border: #D9D9D9;
--3dx-border-light: #E6E6E6;
/* Typography */
--3dx-font-family: "3DSwift", "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
--3dx-font-size-xs: 13px;
--3dx-font-size-sm: 14px;
--3dx-font-size-md: 16px;
--3dx-font-size-lg: 18px;
--3dx-font-size-xl: 20px;
/* Spacing */
--3dx-spacing-xs: 4px;
--3dx-spacing-sm: 8px;
--3dx-spacing-md: 12px;
--3dx-spacing-lg: 16px;
--3dx-spacing-xl: 20px;
--3dx-spacing-2xl: 24px;
/* Border Radius */
--3dx-radius-sm: 2px;
--3dx-radius-md: 4px;
--3dx-radius-lg: 6px;
/* Shadows */
--3dx-shadow-sm: 0 1px 2px rgba(0,0,0,0.08);
--3dx-shadow-md: 0 2px 8px rgba(0,0,0,0.12);
--3dx-shadow-lg: 0 4px 16px rgba(0,0,0,0.16);
/* Transitions */
--3dx-transition: all 0.15s ease;
/* Component Heights */
--3dx-height-sm: 28px;
--3dx-height-md: 36px;
--3dx-height-lg: 44px;
}
/* Reset */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-sm);
color: var(--3dx-gray-900);
background: var(--3dx-background);
line-height: 1.6;
overflow: hidden;
height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ======================================================
3DX GLOBAL HEADER (TOP BAR)
====================================================== */
.global-header {
height: 48px;
background: linear-gradient(90deg, #1A1A1A 0%, #2D2D2D 100%);
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-md);
gap: var(--3dx-spacing-md);
z-index: 1000;
position: relative;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.global-header .brand-logo {
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
}
.global-header .brand-icon {
width: 32px;
height: 32px;
border-radius: 6px;
background: linear-gradient(135deg, #0078BE 0%, #00C3FF 100%);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: var(--3dx-transition);
}
.global-header .brand-icon:hover {
transform: scale(1.05);
box-shadow: 0 0 12px rgba(0,120,190,0.5);
}
.global-header .brand-icon svg {
width: 18px;
height: 18px;
fill: #fff;
}
.global-header .app-title {
color: #fff;
font-size: var(--3dx-font-size-lg);
font-weight: 500;
letter-spacing: 0.3px;
}
.global-header .app-title strong {
color: #00C3FF;
font-weight: 700;
}
.global-header .header-spacer {
flex: 1;
}
.global-header .user-menu {
display: flex;
align-items: center;
gap: var(--3dx-spacing-md);
font-size: var(--3dx-font-size-sm);
color: #E0E0E0;
}
.global-header .user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--3dx-blue);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--3dx-font-size-xs);
font-weight: 600;
border: 2px solid rgba(255,255,255,0.2);
cursor: pointer;
transition: var(--3dx-transition);
}
.global-header .user-avatar:hover {
border-color: #fff;
transform: scale(1.05);
}
.global-header .user-info {
display: flex;
flex-direction: column;
gap: 1px;
}
.global-header .user-name {
font-size: var(--3dx-font-size-sm);
font-weight: 500;
}
.global-header .user-role {
font-size: 10px;
color: var(--3dx-gray-400);
}
/* ======================================================
3DX APP SHELL
====================================================== */
.app-shell {
height: calc(100vh - 48px);
display: flex;
flex-direction: column;
background: var(--3dx-surface);
overflow: hidden;
}
/* ======================================================
3DX TOOLBAR
====================================================== */
.toolbar {
height: 44px;
min-height: 44px;
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-lg);
gap: var(--3dx-spacing-sm);
border-bottom: 1px solid var(--3dx-border);
box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset;
}
.toolbar .toolbar-icon {
color: var(--3dx-gray-600);
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.toolbar .toolbar-icon:hover {
background: var(--3dx-gray-200);
color: var(--3dx-blue);
}
.toolbar .toolbar-title {
font-size: var(--3dx-font-size-md);
font-weight: 600;
color: var(--3dx-gray-800);
margin-left: var(--3dx-spacing-sm);
}
.toolbar .toolbar-spacer {
flex: 1;
}
/* ======================================================
3DX TABS
====================================================== */
.tabs {
display: flex;
background: var(--3dx-gray-100);
border-bottom: 1px solid var(--3dx-border);
padding: 0;
gap: 0;
align-items: center;
}
.tab {
padding: var(--3dx-spacing-sm) var(--3dx-spacing-xl);
font-size: var(--3dx-font-size-sm);
font-weight: 500;
color: var(--3dx-gray-600);
cursor: pointer;
border: 1px solid transparent;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: var(--3dx-transition);
user-select: none;
white-space: nowrap;
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
min-height: 44px;
position: relative;
}
.tab::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--3dx-blue);
opacity: 0;
transition: var(--3dx-transition);
}
.tab:hover {
color: var(--3dx-blue);
background: var(--3dx-blue-light);
}
.tab.active {
color: var(--3dx-blue);
border-color: var(--3dx-blue);
border-bottom-width: 2px;
font-weight: 600;
background: var(--3dx-surface);
}
.tab.active::before {
opacity: 0;
}
.tab .tab-badge {
font-size: 11px;
background: var(--3dx-gray-300);
color: var(--3dx-gray-700);
padding: 2px 8px;
border-radius: 10px;
margin-left: var(--3dx-spacing-xs);
font-weight: 600;
}
.tab .tab-badge.live {
background: var(--3dx-success-bg);
color: var(--3dx-success);
}
.tab.pca-tab {
display: none;
}
.tab.pca-tab.active {
color: var(--3dx-info);
border-bottom-color: var(--3dx-info);
}
.tabs.locked .tab {
pointer-events: none;
opacity: .45;
}
.tabs.locked .tab.active {
opacity: .55;
}
.tabs-spacer {
flex: 1;
}
.tab-shortcut {
margin-right: var(--3dx-spacing-md);
height: 30px;
padding: 0 var(--3dx-spacing-md);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-sm);
background: var(--3dx-surface);
color: var(--3dx-blue);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-sm);
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: var(--3dx-transition);
}
.tab-shortcut:hover {
border-color: var(--3dx-blue);
background: var(--3dx-blue-light);
}
/* ======================================================
3DX SCREENS / PANELS
====================================================== */
.screen {
flex: 1;
overflow: auto;
display: none;
flex-direction: column;
background: var(--3dx-background);
}
.screen.active {
display: flex;
}
/* ======================================================
3DX CATEGORY CARDS
====================================================== */
.category-panel {
padding: var(--3dx-spacing-2xl);
overflow: auto;
}
.category-panel h3 {
font-size: var(--3dx-font-size-xl);
font-weight: 600;
color: var(--3dx-gray-800);
margin-bottom: var(--3dx-spacing-lg);
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
}
.category-panel h3 svg {
color: var(--3dx-gray-500);
flex-shrink: 0;
}
.category-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: var(--3dx-spacing-md);
}
.category-card {
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
padding: var(--3dx-spacing-lg);
cursor: pointer;
transition: var(--3dx-transition);
background: var(--3dx-surface);
position: relative;
overflow: hidden;
min-height: 140px;
box-shadow: var(--3dx-shadow-sm);
}
.category-card::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--3dx-blue);
opacity: 0;
transition: var(--3dx-transition);
}
.category-card:hover:not(.disabled) {
border-color: var(--3dx-blue);
box-shadow: var(--3dx-shadow-md);
transform: translateY(-1px);
}
.category-card:hover:not(.disabled)::before {
opacity: 1;
}
.category-card.active {
border-color: var(--3dx-blue);
background: var(--3dx-blue-light);
box-shadow: 0 0 0 2px rgba(0,120,190,0.15);
}
.category-card.active::before {
opacity: 1;
}
.category-card.disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--3dx-gray-100);
}
.category-card .card-number {
font-size: 10px;
color: var(--3dx-gray-500);
font-weight: 600;
margin-bottom: var(--3dx-spacing-xs);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.category-card .card-title {
font-size: var(--3dx-font-size-md);
font-weight: 600;
color: var(--3dx-gray-800);
line-height: 1.3;
margin-bottom: var(--3dx-spacing-xs);
}
.category-card.disabled .card-title {
color: var(--3dx-gray-600);
}
.category-card .card-desc {
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-600);
line-height: 1.4;
margin-bottom: var(--3dx-spacing-sm);
}
.category-card .card-badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.category-card .card-badge.soon {
background: var(--3dx-gray-200);
color: var(--3dx-gray-600);
}
.category-card .card-badge.live {
background: var(--3dx-success-bg);
color: var(--3dx-success);
border: 1px solid #C8E6C9;
}
/* ======================================================
3DX MAIN WORKING PANEL
====================================================== */
.main-panel {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel-info-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-xs) var(--3dx-spacing-md);
background: var(--3dx-gray-100);
border-bottom: 1px solid var(--3dx-border);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-600);
}
.panel-info-bar b {
color: var(--3dx-blue);
}
.panel-title {
font-size: var(--3dx-font-size-md);
font-weight: 700;
color: var(--3dx-gray-800);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-lg);
background: var(--3dx-surface);
border-bottom: 1px solid var(--3dx-border-light);
}
.panel-toolbar {
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-lg);
border-bottom: 1px solid var(--3dx-border);
background: var(--3dx-surface);
flex-wrap: wrap;
}
.empty-cancel-row {
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-lg) var(--3dx-spacing-lg);
margin-top: auto;
}
.empty-cancel-row .btn {
height: 36px;
min-width: 138px;
justify-content: center;
font-size: var(--3dx-font-size-sm);
}
/* ======================================================
3DX BUTTONS
====================================================== */
.btn {
height: var(--3dx-height-sm);
padding: 0 var(--3dx-spacing-md);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: var(--3dx-spacing-xs);
border: 1px solid transparent;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
white-space: nowrap;
}
.btn:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.btn-secondary {
background: var(--3dx-surface);
border-color: var(--3dx-border);
color: var(--3dx-gray-700);
}
.btn-secondary:hover:not(:disabled) {
background: var(--3dx-gray-100);
border-color: var(--3dx-gray-400);
color: var(--3dx-gray-900);
}
.btn-primary {
background: var(--3dx-blue);
border-color: var(--3dx-blue);
color: #fff;
}
.btn-primary:hover:not(:disabled) {
background: var(--3dx-blue-hover);
border-color: var(--3dx-blue-hover);
}
.btn-success {
background: var(--3dx-success);
border-color: var(--3dx-success);
color: #fff;
}
.btn-success:hover:not(:disabled) {
background: #157A3D;
border-color: #157A3D;
}
.btn-danger {
background: var(--3dx-surface);
border-color: var(--3dx-border);
color: var(--3dx-error);
}
.btn-danger:hover:not(:disabled) {
background: var(--3dx-error-bg);
border-color: var(--3dx-error);
}
.btn-warning {
background: var(--3dx-warning-bg);
border-color: #FCD34D;
color: var(--3dx-warning);
}
.btn-warning:hover:not(:disabled) {
background: #FEF3C7;
}
.btn-icon {
width: var(--3dx-height-sm);
min-width: var(--3dx-height-sm);
padding: 0;
justify-content: center;
}
.btn-sm {
height: 22px;
padding: 0 var(--3dx-spacing-sm);
font-size: 10px;
}
/* ======================================================
3DX DROP ZONE
====================================================== */
.drop-zone {
border: 2px dashed var(--3dx-border);
margin: var(--3dx-spacing-lg);
padding: var(--3dx-spacing-2xl);
text-align: center;
color: var(--3dx-gray-500);
cursor: pointer;
transition: var(--3dx-transition);
background: var(--3dx-surface);
border-radius: var(--3dx-radius-md);
}
.drop-zone:hover,
.drop-zone.over {
border-color: var(--3dx-blue);
background: var(--3dx-blue-light);
}
.drop-zone svg {
color: var(--3dx-gray-400);
margin-bottom: var(--3dx-spacing-sm);
}
.drop-zone p {
font-size: var(--3dx-font-size-sm);
margin-bottom: var(--3dx-spacing-xs);
}
.drop-zone .drop-zone-or {
font-size: 10px;
color: var(--3dx-gray-400);
margin: var(--3dx-spacing-sm) 0;
}
.drop-zone .drop-zone-btn {
display: inline-flex;
align-items: center;
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
background: var(--3dx-blue);
color: #fff;
border: none;
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
font-weight: 600;
cursor: pointer;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.drop-zone .drop-zone-btn:hover {
background: var(--3dx-blue-hover);
}
/* ======================================================
3DX DATA TABLE
====================================================== */
.table-wrapper {
flex: 1;
overflow: auto;
min-height: 0;
background: var(--3dx-surface);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
margin: 0 var(--3dx-spacing-lg);
}
table.data-table {
width: 100%;
border-collapse: collapse;
font-size: var(--3dx-font-size-xs);
}
table.data-table thead {
position: sticky;
top: 0;
z-index: 10;
}
table.data-table th {
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
border: 1px solid var(--3dx-border);
border-bottom-color: var(--3dx-gray-400);
padding: 0;
text-align: left;
font-weight: 700;
color: var(--3dx-gray-900);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
user-select: none;
position: relative;
}
table.data-table th .th-content {
display: flex;
flex-direction: column;
gap: 2px;
}
table.data-table th .th-label {
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
cursor: pointer;
font-weight: 700;
font-size: 11px;
}
table.data-table th .th-label:hover {
color: var(--3dx-blue);
}
table.data-table th.sortable .th-label {
color: var(--3dx-blue);
}
table.data-table th .th-sort {
opacity: 0;
font-size: 8px;
transition: var(--3dx-transition);
}
table.data-table th:hover .th-sort,
table.data-table th.sortable .th-sort {
opacity: 1;
}
table.data-table th .th-filter {
padding: 2px var(--3dx-spacing-md) var(--3dx-spacing-sm);
border-top: 1px solid var(--3dx-border-light);
display: flex;
align-items: center;
gap: 4px;
}
table.data-table th .th-filter input {
flex: 1;
height: 24px;
padding: 0 var(--3dx-spacing-xs);
border: 1px solid var(--3dx-border);
font-size: 11px;
font-family: var(--3dx-font-family);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
}
table.data-table th .th-filter input:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
table.data-table th .th-filter input::placeholder {
color: var(--3dx-gray-400);
}
table.data-table th .th-resize {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
background: transparent;
z-index: 1;
}
table.data-table th .th-resize:hover {
background: var(--3dx-blue);
}
table.data-table th.group-start {
border-left: 2px solid var(--3dx-gray-400);
}
table.data-table td {
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border-light);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
vertical-align: middle;
color: var(--3dx-gray-900);
font-size: var(--3dx-font-size-xs);
}
table.data-table td.group-start {
border-left: 2px solid var(--3dx-gray-300);
}
table.data-table tr:nth-child(even) td {
background: var(--3dx-gray-50);
}
table.data-table tr:hover td {
background: var(--3dx-blue-light);
}
table.data-table tr.selected td {
background: #D6E4F0;
}
table.data-table tr.pca-selected td {
background: #D6E4F0;
}
table.data-table td.changed {
background: #FCE4E4 !important;
}
table.data-table td.editable {
cursor: pointer;
}
table.data-table td.editable:hover {
outline: 1px solid var(--3dx-blue);
outline-offset: -1px;
}
table.data-table td.editable input,
table.data-table td.editable select {
width: 100%;
height: 22px;
border: 1px solid var(--3dx-blue);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
padding: 0 var(--3dx-spacing-xs);
outline: none;
border-radius: var(--3dx-radius-sm);
}
table.data-table td.editable select {
cursor: pointer;
}
/* Table column widths */
.col-check {
width: 28px;
min-width: 28px;
max-width: 28px;
text-align: center;
}
.col-check input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: var(--3dx-blue);
cursor: pointer;
}
.col-action {
width: 32px;
min-width: 32px;
text-align: center;
}
.remove-btn {
width: 20px;
height: 20px;
border: 1px solid var(--3dx-border);
background: var(--3dx-surface);
color: var(--3dx-gray-500);
cursor: pointer;
font-size: 14px;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.remove-btn:hover {
border-color: var(--3dx-error);
color: var(--3dx-error);
background: var(--3dx-error-bg);
}
.col-wide {
min-width: 160px;
max-width: 260px;
}
/* Empty state */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 140px;
color: var(--3dx-gray-400);
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-2xl);
}
.empty-state p {
font-size: var(--3dx-font-size-xs);
text-align: center;
}
/* ======================================================
3DX CHANGE ACTION PANEL
====================================================== */
.change-panel {
padding: var(--3dx-spacing-lg);
border-top: 1px solid var(--3dx-border);
background: var(--3dx-gray-50);
flex-shrink: 0;
}
.change-panel h4 {
font-size: 10px;
font-weight: 700;
color: var(--3dx-gray-700);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--3dx-spacing-md);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
.change-panel .change-warning {
font-size: 10px;
color: var(--3dx-warning);
background: var(--3dx-warning-bg);
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
margin-bottom: var(--3dx-spacing-md);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
border-left: 3px solid var(--3dx-warning);
border-radius: var(--3dx-radius-sm);
}
.change-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--3dx-spacing-lg) var(--3dx-spacing-xl);
}
.change-grid h5 {
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--3dx-spacing-sm);
padding-bottom: var(--3dx-spacing-xs);
border-bottom: 1px solid var(--3dx-border);
}
.form-row {
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
margin-bottom: var(--3dx-spacing-sm);
}
.form-row label {
font-size: 10px;
font-weight: 600;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.3px;
min-width: 85px;
text-align: right;
}
.form-row input,
.form-row select {
flex: 1;
height: var(--3dx-height-sm);
padding: 0 var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
background: var(--3dx-surface);
}
.form-row input:focus,
.form-row select:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
.form-row input[readonly] {
background: var(--3dx-gray-100);
color: var(--3dx-gray-500);
cursor: not-allowed;
}
.form-row .required-mark {
color: var(--3dx-error);
margin-left: 2px;
font-size: 10px;
}
/* ======================================================
3DX ROUTE INFO BANNER
====================================================== */
.route-banner {
display: flex;
align-items: flex-start;
gap: var(--3dx-spacing-sm);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
background: var(--3dx-info-bg);
border-top: 1px solid #BBDEFB;
border-bottom: 1px solid #BBDEFB;
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-700);
flex-shrink: 0;
}
.route-banner svg {
color: var(--3dx-info);
flex-shrink: 0;
margin-top: 1px;
}
.route-banner strong {
color: var(--3dx-blue);
}
/* ======================================================
3DX ACTION BAR
====================================================== */
.action-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-sm) var(--3dx-spacing-lg);
background: var(--3dx-gray-100);
border-top: 1px solid var(--3dx-border);
flex-shrink: 0;
}
/* ======================================================
3DX MODAL OVERLAY
====================================================== */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(26, 26, 26, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(2px);
}
.modal-overlay.open {
display: flex;
}
.modal {
background: var(--3dx-surface);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
box-shadow: var(--3dx-shadow-lg);
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 85vh;
}
.modal-header {
height: 40px;
min-height: 40px;
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
border-bottom: 1px solid var(--3dx-border);
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-md);
gap: var(--3dx-spacing-sm);
}
.modal-header .modal-title {
font-size: var(--3dx-font-size-sm);
font-weight: 600;
color: var(--3dx-gray-800);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
.modal-header .modal-spacer {
flex: 1;
}
.modal-header .modal-close {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: var(--3dx-gray-500);
cursor: pointer;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.modal-header .modal-close:hover {
color: var(--3dx-error);
background: var(--3dx-error-bg);
}
.modal-context {
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
background: var(--3dx-gray-50);
border-bottom: 1px solid var(--3dx-border);
font-size: 10px;
color: var(--3dx-gray-600);
display: flex;
gap: var(--3dx-spacing-xs);
align-items: center;
}
.modal-context b {
color: var(--3dx-blue);
font-weight: 600;
}
.modal-context .separator {
color: var(--3dx-gray-400);
font-size: 8px;
}
.modal-body {
flex: 1;
overflow: auto;
min-height: 0;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
background: var(--3dx-gray-100);
border-top: 1px solid var(--3dx-border);
}
.modal-footer .footer-info {
font-size: 10px;
color: var(--3dx-gray-500);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
.modal-footer .footer-actions {
display: flex;
gap: var(--3dx-spacing-sm);
}
/* ======================================================
3DX SEARCH AREA
====================================================== */
.search-area {
padding: var(--3dx-spacing-md);
border-bottom: 1px solid var(--3dx-border);
background: var(--3dx-surface);
}
.search-row {
display: flex;
gap: var(--3dx-spacing-md);
align-items: flex-end;
}
.search-field {
flex: 1;
max-width: 280px;
position: relative;
}
.search-field label {
display: block;
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 2px;
}
.search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-input-wrapper svg {
position: absolute;
left: var(--3dx-spacing-sm);
color: var(--3dx-gray-400);
pointer-events: none;
width: 12px;
height: 12px;
}
.search-input-wrapper input {
width: 100%;
height: var(--3dx-height-md);
padding: 0 var(--3dx-spacing-lg) 0 var(--3dx-spacing-xl);
border: 1px solid var(--3dx-border);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
}
.search-input-wrapper input::placeholder {
color: var(--3dx-gray-400);
}
.search-input-wrapper input:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
.search-input-wrapper input:disabled {
background: var(--3dx-gray-100);
cursor: not-allowed;
}
.search-input-clear {
position: absolute;
right: 4px;
background: none;
border: none;
color: var(--3dx-gray-400);
cursor: pointer;
width: 18px;
height: 18px;
display: none;
align-items: center;
justify-content: center;
font-size: 14px;
border-radius: var(--3dx-radius-sm);
}
.search-input-clear:hover {
color: var(--3dx-gray-700);
background: var(--3dx-gray-200);
}
.search-suggestions {
position: absolute;
top: calc(100% + 2px);
left: 0;
right: 0;
background: var(--3dx-surface);
border: 1px solid var(--3dx-border);
border-top: none;
max-height: 180px;
overflow-y: auto;
z-index: 100;
display: none;
border-radius: 0 0 var(--3dx-radius-sm) var(--3dx-radius-sm);
box-shadow: var(--3dx-shadow-md);
}
.search-suggestions.open {
display: block;
}
.suggestion-item {
padding: var(--3dx-spacing-xs) var(--3dx-spacing-md);
cursor: pointer;
font-size: var(--3dx-font-size-xs);
display: flex;
gap: var(--3dx-spacing-sm);
transition: var(--3dx-transition);
}
.suggestion-item:hover {
background: var(--3dx-blue-light);
}
.suggestion-item .suggestion-code {
font-weight: 700;
color: var(--3dx-blue);
min-width: 70px;
}
.suggestion-item .suggestion-label {
color: var(--3dx-gray-700);
}
.search-tags {
margin-top: var(--3dx-spacing-sm);
min-height: 20px;
display: flex;
gap: var(--3dx-spacing-xs);
flex-wrap: wrap;
}
.search-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: var(--3dx-blue-light);
border: 1px solid #BDD0F0;
font-size: 10px;
font-weight: 500;
color: var(--3dx-blue);
border-radius: 10px;
}
.search-tag button {
background: none;
border: none;
cursor: pointer;
color: var(--3dx-blue);
font-size: 12px;
line-height: 1;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.search-tag button:hover {
color: var(--3dx-error);
}
/* ======================================================
3DX PANELS (Split View)
====================================================== */
.panels {
flex: 1;
display: flex;
overflow: hidden;
min-height: 0;
}
.panel {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-xs) var(--3dx-spacing-md);
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
border-bottom: 1px solid var(--3dx-border);
min-height: 30px;
}
.panel-title-text {
font-size: 10px;
font-weight: 700;
color: var(--3dx-gray-700);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.panel-count {
font-size: 9px;
color: var(--3dx-gray-500);
background: var(--3dx-gray-200);
padding: 0 var(--3dx-spacing-xs);
border-radius: 2px;
font-weight: 600;
}
.panel-content {
flex: 1;
overflow: auto;
background: var(--3dx-surface);
}
.panel-divider {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-sm);
background: var(--3dx-gray-100);
border-left: 1px solid var(--3dx-border);
border-right: 1px solid var(--3dx-border);
}
.transfer-btn {
width: 28px;
height: 28px;
border: 1px solid var(--3dx-border);
background: var(--3dx-surface);
color: var(--3dx-gray-600);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.transfer-btn:hover:not(:disabled) {
border-color: var(--3dx-blue);
color: var(--3dx-blue);
background: var(--3dx-blue-light);
}
.transfer-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
pointer-events: none;
}
/* ======================================================
3DX SUMMARY MODAL
====================================================== */
.summary-modal {
width: 95vw;
max-width: 1200px;
height: 80vh;
max-height: 750px;
}
.summary-body {
padding: var(--3dx-spacing-lg);
overflow: auto;
}
.summary-body h4 {
font-size: 10px;
font-weight: 700;
color: var(--3dx-gray-700);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--3dx-spacing-sm);
padding-bottom: var(--3dx-spacing-xs);
border-bottom: 1px solid var(--3dx-border);
}
.summary-row {
display: flex;
gap: var(--3dx-spacing-sm);
margin-bottom: var(--3dx-spacing-xs);
font-size: var(--3dx-font-size-xs);
}
.summary-label {
font-weight: 700;
color: var(--3dx-gray-600);
min-width: 120px;
text-align: right;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.summary-value {
color: var(--3dx-gray-900);
}
table.summary-table {
width: 100%;
border-collapse: collapse;
font-size: 10px;
margin-top: var(--3dx-spacing-md);
}
table.summary-table th {
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
text-align: left;
font-weight: 700;
color: var(--3dx-gray-700);
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.5px;
border: 1px solid var(--3dx-border);
}
table.summary-table td {
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border-light);
}
table.summary-table .change-highlight {
color: var(--3dx-error);
font-weight: 600;
}
/* ======================================================
3DX ROUTE MODAL
====================================================== */
.route-modal {
width: 620px;
max-width: 95vw;
}
.route-modal-header {
height: 44px;
background: linear-gradient(90deg, var(--3dx-blue) 0%, var(--3dx-blue-dark) 100%);
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-md);
gap: var(--3dx-spacing-sm);
}
.route-modal-header span {
font-size: var(--3dx-font-size-sm);
font-weight: 600;
color: #fff;
flex: 1;
}
.route-modal-header .route-close {
color: rgba(255,255,255,0.7);
cursor: pointer;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.route-modal-header .route-close:hover {
color: #fff;
background: rgba(255,255,255,0.15);
}
.route-modal-body {
padding: var(--3dx-spacing-lg);
display: flex;
flex-direction: column;
gap: var(--3dx-spacing-md);
}
.route-section {
border: 1px solid var(--3dx-border);
background: var(--3dx-gray-50);
padding: var(--3dx-spacing-md);
border-radius: var(--3dx-radius-sm);
}
.route-section h5 {
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--3dx-spacing-sm);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
.route-section h5 svg {
color: var(--3dx-blue);
}
.route-field {
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
margin-bottom: var(--3dx-spacing-sm);
}
.route-field:last-child {
margin-bottom: 0;
}
.route-field label {
min-width: 100px;
text-align: right;
font-size: 10px;
font-weight: 600;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.route-field input,
.route-field select,
.route-field textarea {
flex: 1;
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
background: var(--3dx-surface);
}
.route-field input:focus,
.route-field select:focus,
.route-field textarea:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
.route-field input[readonly] {
background: var(--3dx-gray-100);
color: var(--3dx-gray-500);
cursor: not-allowed;
}
.route-field textarea {
height: 60px;
resize: vertical;
line-height: 1.4;
}
.route-change-list {
font-size: 10px;
border: 1px solid var(--3dx-border);
background: var(--3dx-surface);
max-height: 120px;
overflow: auto;
border-radius: var(--3dx-radius-sm);
}
.route-change-list .rcl-row {
display: flex;
gap: 0;
border-bottom: 1px solid var(--3dx-border-light);
}
.route-change-list .rcl-row:last-child {
border-bottom: none;
}
.route-change-list .rcl-row span {
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
border-right: 1px solid var(--3dx-border-light);
}
.route-change-list .rcl-row span:last-child {
border-right: none;
}
.route-change-list .rcl-row .rc-op {
min-width: 140px;
font-weight: 600;
color: var(--3dx-gray-900);
background: var(--3dx-gray-100);
}
.route-change-list .rcl-row .rc-attr {
min-width: 100px;
color: var(--3dx-gray-600);
}
.route-change-list .rcl-row .rc-old {
color: var(--3dx-error);
min-width: 100px;
text-decoration: line-through;
}
.route-change-list .rcl-row .rc-new {
color: var(--3dx-success);
font-weight: 700;
flex: 1;
}
.route-modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-md);
background: var(--3dx-gray-100);
border-top: 1px solid var(--3dx-border);
}
/* ======================================================
3DX TOAST
====================================================== */
.toast {
position: fixed;
top: 56px;
right: var(--3dx-spacing-md);
background: var(--3dx-error-bg);
border: 1px solid #FFCDD2;
border-left: 3px solid var(--3dx-error);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
font-size: var(--3dx-font-size-sm);
color: var(--3dx-error);
z-index: 2000;
opacity: 0;
transform: translateY(-6px);
transition: var(--3dx-transition);
pointer-events: none;
border-radius: var(--3dx-radius-sm);
box-shadow: var(--3dx-shadow-md);
}
.toast.show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.toast.ok {
background: var(--3dx-success-bg);
border-color: #C8E6C9;
border-left-color: var(--3dx-success);
color: var(--3dx-success);
}
.toast.info {
background: var(--3dx-info-bg);
border-color: #BBDEFB;
border-left-color: var(--3dx-info);
color: var(--3dx-blue);
}
/* ======================================================
3DX CONFIRM MODAL
====================================================== */
.confirm-overlay {
position: fixed;
inset: 0;
background: rgba(26, 26, 26, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1300;
backdrop-filter: blur(2px);
}
.confirm-overlay.open {
display: flex;
}
.confirm-box {
background: var(--3dx-surface);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
padding: var(--3dx-spacing-xl);
text-align: center;
max-width: 360px;
box-shadow: var(--3dx-shadow-lg);
}
.confirm-box p {
font-size: var(--3dx-font-size-md);
color: var(--3dx-gray-900);
margin-bottom: var(--3dx-spacing-lg);
}
.confirm-actions {
display: flex;
gap: var(--3dx-spacing-md);
justify-content: center;
}
/* ======================================================
3DX SUCCESS MODAL
====================================================== */
.success-overlay {
position: fixed;
inset: 0;
background: rgba(26, 26, 26, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1200;
backdrop-filter: blur(2px);
}
.success-overlay.open {
display: flex;
}
.success-box {
background: var(--3dx-surface);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
padding: var(--3dx-spacing-2xl);
text-align: center;
max-width: 420px;
box-shadow: var(--3dx-shadow-lg);
}
.success-box svg {
color: var(--3dx-success);
margin-bottom: var(--3dx-spacing-md);
}
.success-box h3 {
font-size: 20px;
color: var(--3dx-gray-900);
margin-bottom: var(--3dx-spacing-xs);
}
.success-box p {
font-size: var(--3dx-font-size-sm);
color: var(--3dx-gray-600);
margin-bottom: var(--3dx-spacing-sm);
}
.success-box .ref-box {
background: var(--3dx-gray-100);
border: 1px solid var(--3dx-border);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-lg);
font-size: var(--3dx-font-size-sm);
margin: var(--3dx-spacing-md) 0 var(--3dx-spacing-lg);
color: var(--3dx-blue);
font-weight: 700;
border-radius: var(--3dx-radius-sm);
display: inline-block;
}
.success-box .ref-note {
font-size: 10px;
color: var(--3dx-gray-500);
margin-bottom: var(--3dx-spacing-lg);
}
.success-actions {
display: flex;
gap: var(--3dx-spacing-md);
justify-content: center;
}
/* ======================================================
3DX SCROLLBAR
====================================================== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--3dx-gray-100);
}
::-webkit-scrollbar-thumb {
background: var(--3dx-gray-400);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--3dx-gray-500);
}
/* ======================================================
PCA TRACKER SCREEN (3DX Style)
====================================================== */
.pca-screen {
flex: 1;
overflow: hidden;
display: none;
flex-direction: column;
}
.pca-screen.active {
display: flex;
}
/* PCA Header */
.pca-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--3dx-spacing-sm) var(--3dx-spacing-lg);
background: var(--3dx-gray-100);
border-bottom: 1px solid var(--3dx-border);
flex-shrink: 0;
}
.pca-header-left {
display: flex;
align-items: center;
gap: var(--3dx-spacing-md);
}
.pca-header-left .pca-title-text {
font-size: var(--3dx-font-size-md);
font-weight: 700;
color: var(--3dx-gray-800);
}
.pca-header-left .pca-subtitle {
font-size: 10px;
color: var(--3dx-gray-500);
}
.pca-header-right {
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
/* KPI Strip */
.pca-kpi {
display: flex;
gap: 0;
border-bottom: 1px solid var(--3dx-border);
flex-shrink: 0;
background: var(--3dx-surface);
}
.pca-kpi-card {
flex: 1;
padding: var(--3dx-spacing-md) var(--3dx-spacing-lg);
border-right: 1px solid var(--3dx-border);
background: var(--3dx-surface);
cursor: pointer;
transition: var(--3dx-transition);
}
.pca-kpi-card:last-child {
border-right: none;
}
.pca-kpi-card:hover {
background: var(--3dx-gray-50);
}
.pca-kpi-card.active {
background: var(--3dx-blue-light);
border-bottom: 2px solid var(--3dx-blue);
}
.pca-kpi-card .kpi-value {
font-size: 26px;
font-weight: 700;
color: var(--3dx-gray-900);
line-height: 1;
}
.pca-kpi-card .kpi-label {
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-500);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: var(--3dx-spacing-xs);
}
.pca-kpi-card.k-pending .kpi-value {
color: var(--3dx-warning);
}
.pca-kpi-card.k-approved .kpi-value {
color: var(--3dx-success);
}
.pca-kpi-card.k-rejected .kpi-value {
color: var(--3dx-error);
}
.pca-kpi-card.k-all .kpi-value {
color: var(--3dx-blue);
}
/* Filters */
.pca-filters {
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
border-bottom: 1px solid var(--3dx-border);
background: var(--3dx-gray-50);
flex-shrink: 0;
}
.pca-filters label {
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.pca-filters input,
.pca-filters select {
height: var(--3dx-height-sm);
padding: 0 var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
background: var(--3dx-surface);
}
.pca-filters input {
width: 220px;
}
.pca-filters input:focus,
.pca-filters select:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
.pca-filters .filter-sep {
width: 1px;
height: 16px;
background: var(--3dx-gray-300);
margin: 0 var(--3dx-spacing-xs);
}
/* Table Wrapper */
.pca-table-wrapper {
flex: 1;
overflow: auto;
min-height: 0;
background: var(--3dx-surface);
margin: var(--3dx-spacing-md);
border: 1px solid var(--3dx-border);
border-radius: var(--3dx-radius-md);
}
/* PCA Table */
table.pca-table {
width: 100%;
border-collapse: collapse;
font-size: var(--3dx-font-size-xs);
}
table.pca-table thead {
position: sticky;
top: 0;
z-index: 10;
}
table.pca-table th {
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
border: 1px solid var(--3dx-border);
border-bottom-color: var(--3dx-gray-400);
padding: 0;
text-align: left;
font-weight: 700;
color: var(--3dx-gray-900);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
cursor: pointer;
user-select: none;
position: relative;
}
table.pca-table th .th-content {
display: flex;
flex-direction: column;
gap: 2px;
}
table.pca-table th .th-label {
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
font-weight: 700;
}
table.pca-table th .th-label:hover {
color: var(--3dx-blue);
}
table.pca-table th .th-sort {
opacity: 0;
font-size: 8px;
transition: var(--3dx-transition);
}
table.pca-table th:hover .th-sort,
table.pca-table th.sortable .th-sort {
opacity: 1;
}
table.pca-table th .th-filter {
padding: 2px var(--3dx-spacing-md) var(--3dx-spacing-sm);
border-top: 1px solid var(--3dx-border-light);
}
table.pca-table th .th-filter input {
width: 100%;
height: 24px;
padding: 0 var(--3dx-spacing-xs);
border: 1px solid var(--3dx-border);
font-size: 10px;
font-family: var(--3dx-font-family);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
}
table.pca-table th .th-filter input:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
table.pca-table th .th-filter input::placeholder {
color: var(--3dx-gray-400);
}
table.pca-table th .th-resize {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
background: transparent;
z-index: 1;
}
table.pca-table th .th-resize:hover {
background: var(--3dx-blue);
}
table.pca-table td {
padding: var(--3dx-spacing-sm) var(--3dx-spacing-md);
border: 1px solid var(--3dx-border-light);
color: var(--3dx-gray-900);
vertical-align: middle;
}
table.pca-table tr:nth-child(even) td {
background: var(--3dx-gray-50);
}
table.pca-table tr:hover td {
background: var(--3dx-blue-light);
cursor: pointer;
}
/* Status Badges */
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
font-size: 9px;
font-weight: 700;
border-radius: 10px;
white-space: nowrap;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.badge::before {
content: '';
width: 5px;
height: 5px;
border-radius: 50%;
background: currentColor;
}
.badge-pending {
background: var(--3dx-warning-bg);
color: var(--3dx-warning);
border: 1px solid #FCD34D;
}
.badge-approved {
background: var(--3dx-success-bg);
color: var(--3dx-success);
border: 1px solid #A5D6A7;
}
.badge-rejected {
background: var(--3dx-error-bg);
color: var(--3dx-error);
border: 1px solid #FFCDD2;
}
.badge-draft {
background: var(--3dx-gray-200);
color: var(--3dx-gray-600);
border: 1px solid var(--3dx-gray-400);
}
/* PCA Empty State */
.pca-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--3dx-gray-400);
gap: var(--3dx-spacing-sm);
padding: var(--3dx-spacing-2xl);
}
.pca-empty-state svg {
color: var(--3dx-gray-300);
}
.pca-empty-state p {
font-size: var(--3dx-font-size-xs);
text-align: center;
max-width: 280px;
line-height: 1.4;
}
/* PCA Drawer */
.pca-drawer {
position: fixed;
top: 48px;
right: 0;
bottom: 0;
width: 560px;
background: var(--3dx-surface);
border-left: 1px solid var(--3dx-border);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.2s ease;
z-index: 900;
box-shadow: var(--3dx-shadow-lg);
}
.pca-drawer.open {
transform: translateX(0);
}
.pca-drawer-header {
height: 44px;
background: linear-gradient(90deg, var(--3dx-blue) 0%, var(--3dx-blue-dark) 100%);
display: flex;
align-items: center;
padding: 0 var(--3dx-spacing-md);
gap: var(--3dx-spacing-sm);
}
.pca-drawer-header span {
font-size: var(--3dx-font-size-sm);
font-weight: 600;
color: #fff;
flex: 1;
}
.pca-drawer-close {
color: rgba(255,255,255,0.7);
cursor: pointer;
font-size: 18px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--3dx-radius-sm);
transition: var(--3dx-transition);
}
.pca-drawer-close:hover {
color: #fff;
background: rgba(255,255,255,0.15);
}
.pca-drawer-body {
flex: 1;
overflow-y: auto;
padding: var(--3dx-spacing-lg);
}
.drawer-section {
margin-bottom: var(--3dx-spacing-lg);
}
.drawer-section h5 {
font-size: 9px;
font-weight: 700;
color: var(--3dx-gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--3dx-spacing-sm);
padding-bottom: var(--3dx-spacing-xs);
border-bottom: 1px solid var(--3dx-border);
display: flex;
align-items: center;
gap: var(--3dx-spacing-xs);
}
.drawer-row {
display: flex;
gap: var(--3dx-spacing-sm);
margin-bottom: var(--3dx-spacing-xs);
font-size: var(--3dx-font-size-xs);
}
.drawer-label {
font-weight: 700;
color: var(--3dx-gray-600);
min-width: 110px;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.drawer-value {
color: var(--3dx-gray-900);
flex: 1;
}
.drawer-change-table {
width: 100%;
border-collapse: collapse;
font-size: 10px;
}
.drawer-change-table th {
background: linear-gradient(180deg, #F8F9FA 0%, #F0F1F3 100%);
border: 1px solid var(--3dx-border);
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
font-size: 9px;
text-transform: uppercase;
font-weight: 700;
color: var(--3dx-gray-700);
letter-spacing: 0.3px;
}
.drawer-change-table td {
border: 1px solid var(--3dx-border-light);
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
}
.drawer-change-table .old-value {
color: var(--3dx-error);
text-decoration: line-through;
}
.drawer-change-table .new-value {
color: var(--3dx-success);
font-weight: 600;
}
/* Route Timeline */
.route-timeline {
display: flex;
flex-direction: column;
gap: 0;
}
.timeline-step {
display: flex;
gap: var(--3dx-spacing-md);
align-items: flex-start;
padding-bottom: var(--3dx-spacing-md);
position: relative;
}
.timeline-step:not(:last-child)::before {
content: '';
position: absolute;
left: 13px;
top: 24px;
bottom: 0;
width: 1px;
background: var(--3dx-border);
}
.timeline-dot {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
border: 2px solid transparent;
background: var(--3dx-gray-100);
}
.timeline-dot.done {
background: var(--3dx-success-bg);
border-color: var(--3dx-success);
color: var(--3dx-success);
}
.timeline-dot.active {
background: var(--3dx-blue-light);
border-color: var(--3dx-blue);
color: var(--3dx-blue);
}
.timeline-dot.wait {
background: var(--3dx-gray-100);
border-color: var(--3dx-gray-300);
color: var(--3dx-gray-400);
}
.timeline-dot.rejected {
background: var(--3dx-error-bg);
border-color: var(--3dx-error);
color: var(--3dx-error);
}
.timeline-info {
flex: 1;
padding-top: 2px;
}
.timeline-info .ti-who {
font-size: var(--3dx-font-size-xs);
font-weight: 600;
color: var(--3dx-gray-900);
}
.timeline-info .ti-role {
font-size: 9px;
color: var(--3dx-gray-500);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.timeline-info .ti-ts {
font-size: 9px;
color: var(--3dx-gray-400);
margin-top: 2px;
}
.timeline-info .ti-comment {
font-size: 10px;
color: var(--3dx-gray-700);
background: var(--3dx-gray-50);
border: 1px solid var(--3dx-border-light);
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
margin-top: var(--3dx-spacing-xs);
border-radius: var(--3dx-radius-sm);
}
/* PCA Drawer Actions */
.pca-drawer-actions {
padding: var(--3dx-spacing-md);
border-top: 1px solid var(--3dx-border);
background: var(--3dx-gray-50);
display: flex;
align-items: center;
gap: var(--3dx-spacing-sm);
flex-shrink: 0;
}
.pca-drawer-actions textarea {
flex: 1;
height: 48px;
resize: none;
padding: var(--3dx-spacing-xs) var(--3dx-spacing-sm);
border: 1px solid var(--3dx-border);
font-family: var(--3dx-font-family);
font-size: var(--3dx-font-size-xs);
color: var(--3dx-gray-900);
outline: none;
border-radius: var(--3dx-radius-sm);
}
.pca-drawer-actions textarea:focus {
border-color: var(--3dx-blue);
box-shadow: 0 0 0 2px var(--3dx-blue-light);
}
.pca-drawer-actions textarea::placeholder {
color: var(--3dx-gray-400);
}
</style>
</head>
<body>
<div class="toast" id="toast"><span id="toastMsg"></span></div>
<!-- 3DX GLOBAL HEADER -->
<header class="global-header">
<div class="brand-logo">
<div class="brand-icon" title="3DEXPERIENCE">
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
</div>
<span class="app-title"><strong>3D</strong>EXPERIENCE</span>
</div>
<div class="header-spacer"></div>
<div class="user-menu">
<div class="user-info">
<span class="user-name">ME Engineer</span>
<span class="user-role">Jaguar Motors UK</span>
</div>
<div class="user-avatar" title="ME Engineer">ME</div>
</div>
</header>
<!-- 3DX APP SHELL -->
<div class="app-shell">
<!-- 3DX TOOLBAR -->
<div class="toolbar">
<div class="toolbar-icon">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
</div>
<span class="toolbar-title">Manufacturing Execution Change Assistant</span>
<div class="toolbar-spacer"></div>
</div>
<!-- 3DX TABS -->
<div class="tabs" id="tabsBar">
<div class="tab" id="tabM" onclick="sTab('m')">MBOM MFG Change</div>
<div class="tab active" id="tabB" onclick="sTab('b')">BOP MFG Change</div>
<div class="tab pca-tab" id="tabP" onclick="sTab('p')">
PCA Tracker
<span id="pcaBadge" style="display:none;margin-left:5px;background:var(--3dx-warning);color:#fff;font-size:9px;font-weight:700;padding:1px 6px;border-radius:10px">0</span>
</div>
<div class="tabs-spacer"></div>
<button class="tab-shortcut" id="btnPcaShortcut" onclick="openPCAShortcut()" style="display:none">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
PCA Tracker
</button>
</div>
<!-- MBOM Screen -->
<div class="screen" id="scrM">
<div class="category-panel">
<h3>
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg>
MBOM MFG Change - Categories
</h3>
<div class="category-grid">
<div class="category-card">
<div class="card-title">Early / Delay Introduction</div>
<div class="card-desc">Adjust introduction timing for MBOM items</div>
</div>
<div class="category-card">
<div class="card-title">Extend / Delete / False-Out Effectivity</div>
<div class="card-desc">Manage effectivity ranges for MBOM components</div>
</div>
<div class="category-card">
<div class="card-title">Skip / Reintroduction</div>
<div class="card-desc">Skip or reintroduce MBOM components</div>
</div>
</div>
</div>
</div>
<!-- BOP Category Screen -->
<div class="screen active" id="scrBC">
<div class="category-panel">
<h3>
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
BOP MFG Change - Select Category
</h3>
<div class="category-grid">
<div class="category-card active" onclick="selCat()">
<div class="card-title">Operations Attribute Change</div>
<div class="card-desc">Modify attributes of BOP operations (title, description, category, timing, etc.)</div>
<span class="card-badge live">Active</span>
</div>
<div class="category-card disabled">
<div class="card-title">MBOM-BOP Part Assignment / Unassignment</div>
<div class="card-desc">Assign or unassign MBOM parts to BOP operations</div>
<span class="card-badge soon">Coming Soon</span>
</div>
<div class="category-card disabled">
<div class="card-title">Operations Resequencing</div>
<div class="card-desc">Change the sequence order of BOP operations</div>
<span class="card-badge soon">Coming Soon</span>
</div>
<div class="category-card disabled">
<div class="card-title">Line Balancing</div>
<div class="card-desc">Redistribute operations across workstations for optimal cycle time</div>
<span class="card-badge soon">Coming Soon</span>
</div>
<div class="category-card disabled">
<div class="card-title">Create New General System / Operation</div>
<div class="card-desc">Author a new DELMIA General System or operation and place it in the manufacturing process with station context, precedence, and takt-aligned process timing.</div>
<span class="card-badge soon">Coming Soon</span>
</div>
<div class="category-card disabled">
<div class="card-title">Create New Resource</div>
<div class="card-desc">Create a new manufacturing resource (tool, fixture, machine, robot, or labor skill) with plant ownership, capability attributes, and planning data for automotive production readiness.</div>
<span class="card-badge soon">Coming Soon</span>
</div>
<div class="category-card disabled">
<div class="card-title">Resource Assignment / Unassignment</div>
<div class="card-desc">Assign or unassign resources to BOP operations and workstations with DELMIA-valid constraints for capacity, effectivity, and execution continuity on the line.</div>
<span class="card-badge soon">Coming Soon</span>
</div>
</div>
</div>
</div>
<!-- Main Working Window -->
<div class="screen" id="scrMW">
<div class="main-panel">
<div class="panel-info-bar">
<span><b>BOP MFG Change</b> | Category: <b>Operations Attribute Change</b></span>
</div>
<div class="panel-title">Selected Operations for Attribute Change</div>
<div class="panel-toolbar" id="mwTB">
<span id="mwCnt" style="font-size:11px;color:var(--3dx-gray-600);font-weight:600;display:none"></span>
<div style="flex:1"></div>
<span id="bOS" style="display:none"></span>
<button class="btn btn-secondary" id="bAM" onclick="openS()" style="display:none">
<svg width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
Add
</button>
<div style="width:1px;height:16px;background:var(--3dx-gray-300);margin:0 8px;" id="s1" style="display:none"></div>
<button class="btn btn-danger" id="bBD" onclick="bDel()" style="display:none">
<svg width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
Remove
</button>
</div>
<div class="drop-zone" id="dz" ondragover="event.preventDefault();this.classList.add('over')" ondragleave="this.classList.remove('over')" ondrop="event.preventDefault();this.classList.remove('over');simDrop()">
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="1.2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
<p><b>Drag & Drop</b> operations or Work Locations here</p>
<div class="drop-zone-or">— or —</div>
<button class="drop-zone-btn" onclick="openS()">
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
Open Content Search
</button>
</div>
<div class="empty-cancel-row" id="bCancelDock" style="display:none">
<button class="btn btn-danger" onclick="goBack()">Cancel</button>
</div>
<div class="table-wrapper" id="mwTW" style="display:none">
<table class="data-table" id="mwT">
<thead id="mwTH"></thead>
<tbody id="mwTBD"></tbody>
</table>
</div>
<!-- Change Action Information -->
<div class="change-panel" id="caS" style="display:none">
<h4>
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Change Information
</h4>
<div class="change-grid">
<div class="change-col">
<h5>Properties</h5>
<div class="form-row">
<label>Severity<span class="required-mark">*</span></label>
<select id="caSev">
<option selected>Unassigned (Optional)</option>
<option>Medium</option>
<option>Urgent</option>
<option>High</option>
<option>Low</option>
</select>
</div>
<div class="form-row"><label>Due Date<span class="required-mark">*</span></label><input type="date" id="caDD"></div>
</div>
<div class="change-col">
<h5>Route Members</h5>
<div class="form-row"><label>Owner</label><input type="text" id="caOw" value="ME Engineer" readonly></div>
<div class="form-row">
<label>Approver<span class="required-mark">*</span></label>
<select id="caAp">
<option value="">— Select Approver —</option>
<option>James Whitfield (BOP Owner)</option>
<option>Sarah Patel (Manufacturing Mgr)</option>
<option>David Chen (Process Engineer Lead)</option>
<option>Claire Houghton (Plant ME Lead)</option>
<option>Robert Osei (Quality Assurance)</option>
</select>
</div>
<div class="form-row"><label>Informed</label>
<select id="caIn">
<option value="">— Optional —</option>
<option>ME Team – Castle Bromwich</option>
<option>ME Team – Solihull</option>
<option>ME Team – Halewood</option>
<option>Process Engineering Group</option>
</select>
</div>
<div class="form-row"><label>Initiator</label><input type="text" id="caIt" value="ME Engineer" readonly></div>
</div>
</div>
</div>
<!-- Route info banner -->
<div class="route-banner" id="routeBanner" style="display:none">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<div>
<strong>Approval Route will be created on Initiate.</strong> The selected approver will receive a task in their 3DX inbox with the full change summary for review. BOP attributes will only be updated in DELMIA once approved.
</div>
</div>
<!-- Action bar -->
<div class="action-bar" id="aBar" style="display:none">
<button class="btn btn-danger" onclick="cfCancel()">
<svg width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
Cancel
</button>
<button class="btn btn-primary" onclick="doInit()">
<svg width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
Initiate
</button>
</div>
</div>
</div>
<!-- PCA TRACKER SCREEN -->
<div class="pca-screen" id="scrP">
<!-- Header -->
<div class="pca-header">
<div class="pca-header-left">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
<div>
<span class="pca-title-text">PCA Tracker — Pending Change Actions</span>
<span class="pca-subtitle">Manufacturing Space — Jaguar Motors UK</span>
</div>
</div>
<div class="pca-header-right">
<button class="btn btn-secondary" onclick="rPCA()">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>
Refresh
</button>
<button class="btn btn-primary" onclick="exportCSV()">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Export
</button>
</div>
</div>
<!-- KPI Cards -->
<div class="pca-kpi" id="pcaKPI"></div>
<!-- Filters -->
<div class="pca-filters">
<label>Search:</label>
<input type="text" id="pcaSearch" placeholder="Search by ID, plant, approver..." oninput="rPCATable()">
<span class="filter-sep"></span>
<label>Plant:</label>
<select id="pcaPlantF" onchange="rPCATable()">
<option value="">All Plants</option>
<option>Castle Bromwich Assembly</option>
<option>Solihull Manufacturing</option>
<option>Halewood Body & Assembly</option>
<option>Engine Manufacturing Centre</option>
</select>
<span class="filter-sep"></span>
<label>Status:</label>
<select id="pcaStatusF" onchange="rPCATable()">
<option value="">All Statuses</option>
<option value="Pending Approval">Pending Approval</option>
<option value="Approved">Approved</option>
<option value="Rejected">Rejected</option>
</select>
<div style="flex:1"></div>
<span style="font-size:10px;color:var(--3dx-gray-500)" id="pcaRowCount">0 records</span>
</div>
<!-- Table -->
<div class="pca-table-wrapper">
<div class="pca-empty-state" id="pcaEmpty">
<svg width="44" height="44" fill="none" stroke="currentColor" stroke-width="1.2" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
<p>No MECA changes submitted yet.<br>Changes initiated from BOP MFG Change will appear here.</p>
</div>
<table class="pca-table" id="pcaTable" style="display:none">
<thead id="pcaTHEAD">
<tr>
<th data-key="ref" onclick="sortPCA('ref')">
<div class="th-content">
<div class="th-label">PCA Ref # <span id="shref"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('ref',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="date" onclick="sortPCA('date')">
<div class="th-content">
<div class="th-label">Initiated <span id="shdate"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('date',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="initiator" onclick="sortPCA('initiator')">
<div class="th-content">
<div class="th-label">Initiated By <span id="shinitiator"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('initiator',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="plant" onclick="sortPCA('plant')">
<div class="th-content">
<div class="th-label">Plant <span id="shplant"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('plant',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="ops" onclick="sortPCA('ops')">
<div class="th-content">
<div class="th-label">Operations Changed <span id="shops"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('ops',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="attrs" onclick="sortPCA('attrs')">
<div class="th-content">
<div class="th-label">Attributes Modified <span id="shattrs"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('attrs',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="approver" onclick="sortPCA('approver')">
<div class="th-content">
<div class="th-label">Approver <span id="shapprover"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('approver',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="dueDate" onclick="sortPCA('dueDate')">
<div class="th-content">
<div class="th-label">Due Date <span id="shdueDate"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('dueDate',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
<th data-key="status" onclick="sortPCA('status')">
<div class="th-content">
<div class="th-label">Status <span id="shstatus"></span></div>
<div class="th-filter"><input placeholder="Filter..." onclick="event.stopPropagation()" oninput="filterPCA('status',this.value)"></div>
</div>
<div class="th-resize"></div>
</th>
</tr>
</thead>
<tbody id="pcaTBody"></tbody>
</table>
</div>
</div>
</div>
<!-- SEARCH MODAL -->
<div class="modal-overlay" id="sM">
<div class="modal" style="width:95vw;max-width:1400px;height:82vh;max-height:800px">
<div class="modal-header">
<span class="modal-title">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
Select the operation(s) to proceed
</span>
<div class="modal-spacer"></div>
<div class="modal-close" onclick="closeS()">✕</div>
</div>
<div class="modal-context">
<b>MECA</b><span class="separator">▸</span><b>BOP MFG Change</b><span class="separator">▸</span><span>Operations Attribute Change</span><span class="separator">│</span><span>Search by Plant Code and Work Location</span>
</div>
<div class="search-area">
<div class="search-row">
<div class="search-field" id="sfP">
<label>Plant Code</label>
<div class="search-input-wrapper">
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input id="iP" autocomplete="off" spellcheck="false" placeholder="Search plant...">
<button class="search-input-clear" id="cP">✕</button>
</div>
<div class="search-suggestions" id="sgP"></div>
</div>
<div class="search-field" id="sfW">
<label>Work Location</label>
<div class="search-input-wrapper">
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input id="iW" autocomplete="off" spellcheck="false" placeholder="Search work location..." disabled>
<button class="search-input-clear" id="cW">✕</button>
</div>
<div class="search-suggestions" id="sgW"></div>
</div>
<div style="padding-top:8px">
<button class="btn btn-primary" id="ldB" disabled onclick="ldOps()">Load Operations</button>
</div>
</div>
<div class="search-tags" id="sTg"></div>
</div>
<div class="panels">
<div class="panel">
<div class="panel-head">
<div class="panel-title-text">Search Results (Available) <span class="panel-count" id="aC">0</span></div>
</div>
<div class="panel-content" id="aW">
<div class="empty-state" id="aE"><p>Select <b>Plant</b> and <b>Work Location</b> then Load.</p></div>
<table class="data-table" id="aT" style="display:none"><thead id="aTH"></thead><tbody id="aTB"></tbody></table>
</div>
</div>
<div class="panel-divider">
<button class="transfer-btn" id="xA" title="Add →" disabled onclick="xR()">▶</button>
<button class="transfer-btn" id="xR" title="← Remove" disabled onclick="xL()">◀</button>
</div>
<div class="panel">
<div class="panel-head">
<div class="panel-title-text">Selected Operations <span class="panel-count" id="sC">0</span></div>
</div>
<div class="panel-content" id="sW">
<div class="empty-state" id="sE"><p>No operations selected yet.</p></div>
<table class="data-table" id="sT" style="display:none"><thead id="sTH"></thead><tbody id="sTB"></tbody></table>
</div>
</div>
</div>
<div class="modal-footer">
<div class="footer-info">Select operations to load into Main Working Window</div>
<div class="footer-actions">
<button class="btn btn-secondary" onclick="closeS()">Cancel</button>
<button class="btn btn-primary" onclick="prS()">Proceed</button>
</div>
</div>
</div>
</div>
<!-- SUMMARY MODAL -->
<div class="modal-overlay" id="smM">
<div class="modal summary-modal">
<div class="modal-header">
<span class="modal-title">Change Summary — Review Before Initiating Route</span>
<div class="modal-spacer"></div>
<div class="modal-close" onclick="clSm()">✕</div>
</div>
<div class="summary-body" id="smB"></div>
<div class="modal-footer">
<div class="footer-info">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
Approver will review this summary before BOP changes are applied in DELMIA
</div>
<div class="footer-actions">
<button class="btn btn-secondary" onclick="clSm()">Back</button>
<button class="btn btn-primary" onclick="openRouteModal()">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 17a2 2 0 01-2 2H4a2 2 0 01-2-2"/><polyline points="12 17 12 3"/><polyline points="5 10 12 3 19 10"/></svg>
Send for Approval
</button>
</div>
</div>
</div>
</div>
<!-- ROUTE CREATION MODAL -->
<div class="modal-overlay" id="routeModal">
<div class="modal route-modal">
<div class="route-modal-header">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<span>Create Approval Route</span>
<div class="route-close" onclick="closeRouteModal()">✕</div>
</div>
<div class="route-modal-body">
<!-- Route Properties -->
<div class="route-section">
<h5>
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Route Properties
</h5>
<div class="route-field"><label>Route Title</label><input type="text" id="rTitle" readonly></div>
<div class="route-field"><label>Type</label><input type="text" value="BOP Operations Attribute Change" readonly></div>
<div class="route-field"><label>Reference</label><input type="text" id="rRef" readonly></div>
<div class="route-field"><label>Credentials</label><input type="text" value="Manufacturing Space — Jaguar Motors UK" readonly></div>
</div>
<!-- Assignees -->
<div class="route-section">
<h5>
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/></svg>
Assignees
</h5>
<div class="route-field"><label>Initiator</label><input type="text" value="ME Engineer" readonly></div>
<div class="route-field"><label>Approver</label><input type="text" id="rApprover" readonly></div>
<div class="route-field"><label>Informed</label><input type="text" id="rInformed" readonly></div>
</div>
<!-- Change Summary -->
<div class="route-section">
<h5>
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
Change Summary (Visible to Approver)
</h5>
<div id="rChangeSummary" class="route-change-list"></div>
</div>
<!-- Comments -->
<div class="route-section">
<h5>
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
Comment to Approver (Optional)
</h5>
<div class="route-field" style="align-items:flex-start">
<label style="margin-top:5px">Comment</label>
<textarea id="rComment" placeholder="Add context or reason for this change..."></textarea>
</div>
</div>
</div>
<div class="route-modal-footer">
<div style="font-size:10px;color:var(--3dx-gray-500)">Route will be logged in PCA Tracker and approver notified</div>
<div style="display:flex;gap:8px">
<button class="btn btn-secondary" onclick="closeRouteModal()">Cancel</button>
<button class="btn btn-success" onclick="submitRoute()">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 17a2 2 0 01-2 2H4a2 2 0 01-2-2"/><polyline points="12 17 12 3"/><polyline points="5 10 12 3 19 10"/></svg>
Send Route
</button>
</div>
</div>
</div>
</div>
<!-- CONFIRM MODAL -->
<div class="confirm-overlay" id="cfD">
<div class="confirm-box">
<p id="cfMsg">Are you sure?</p>
<div class="confirm-actions">
<button class="btn btn-secondary" id="cfN">No</button>
<button class="btn btn-danger" id="cfY">Yes</button>
</div>
</div>
</div>
<!-- SUCCESS MODAL -->
<div class="success-overlay" id="suO">
<div class="success-box">
<svg width="48" height="48" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
<h3>Route Sent for Approval</h3>
<p>Change Summary has been sent to the approver.</p>
<div class="ref-box" id="suRef">PCA-2024-001</div>
<p class="ref-note">BOP attributes in DELMIA will be updated only once the approver reviews and approves. Track status in the <strong>PCA Tracker</strong> tab.</p>
<div class="success-actions">
<button class="btn btn-secondary" onclick="clSu()">Done</button>
<button class="btn btn-primary" onclick="clSuGoTracker()">View in PCA Tracker</button>
</div>
</div>
</div>
<!-- PCA DETAIL DRAWER -->
<div class="pca-drawer" id="pcaDrawer">
<div class="pca-drawer-header">
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
<span id="drawerTitle">PCA Detail</span>
<div class="pca-drawer-close" onclick="closeDrawer()">✕</div>
</div>
<div class="pca-drawer-body" id="drawerBody"></div>
<div class="pca-drawer-actions" id="drawerActions" style="display:none">
<textarea id="drawerComment" placeholder="Add comment (optional)..."></textarea>
<button class="btn btn-danger" onclick="actOnPCA('reject')">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
Reject
</button>
<button class="btn btn-success" onclick="actOnPCA('approve')">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg>
Approve
</button>
</div>
</div>
<script>
// =====================================================================
// DATA
// =====================================================================
const PL=[{c:"JAGUK01",l:"Castle Bromwich Assembly"},{c:"JAGUK02",l:"Solihull Manufacturing"},{c:"JAGUK03",l:"Halewood Body & Assembly"},{c:"JAGUK04",l:"Engine Manufacturing Centre"}];
const WL={JAGUK01:[{c:"WL-CB-BIW-01",l:"BIW Framing Station A"},{c:"WL-CB-BIW-02",l:"BIW Closure Line"},{c:"WL-CB-PA-01",l:"Paint Shop Booth 1"},{c:"WL-CB-TR-01",l:"Trim & Final Line 1"}],JAGUK02:[{c:"WL-SH-BIW-01",l:"Underbody Assembly"},{c:"WL-SH-GA-01",l:"General Assembly Line 1"},{c:"WL-SH-GA-02",l:"General Assembly Line 2"}],JAGUK03:[{c:"WL-HW-BP-01",l:"Body Press Line"},{c:"WL-HW-WD-01",l:"Weld Line A"}],JAGUK04:[{c:"WL-EM-MC-01",l:"Machining Centre A"},{c:"WL-EM-AS-01",l:"Engine Assembly Line"}]};
const CA=["Assembly","Welding","Fastening","Sealing","Inspection","Material Handling"];
const SH=["Body Shop","Paint Shop","Trim Shop","Final Assembly"],LN=["Line A","Line B","Line C"],ST=["Stn 01","Stn 02","Stn 03","Stn 04","Stn 05"];
function gOps(pc,wc){const pl=PL.find(p=>p.c===pc)?.l||pc,wl=WL[pc]?.find(w=>w.c===wc)?.l||wc,n=8+Math.floor(Math.random()*5),o=[];for(let i=0;i<n;i++){const ct=CA[i%CA.length];o.push({id:`OP-${wc}-${String(i+1).padStart(3,'0')}`,plant:pl,shop:SH[i%SH.length],line:LN[i%LN.length],station:ST[i%ST.length],workLocation:wl,opTitle:`${ct} Op ${String(i+1).padStart(2,'0')}`,name:`GOP_${wc.replace(/-/g,'_')}_${String(i+1).padStart(3,'0')}`,description:DE[i%DE.length],opCategory:ct,parallelSetId:i%3===0?`PSET-${String(Math.floor(i/3)+1).padStart(2,'0')}`:"—",psetNumber:i%3===0?String(Math.floor(i/3)+1):"—",numShots:String(1+(i%4)),sapConsumption:i%2===0?"Yes":"No",estimatedTime:`${(1.2+i*.3).toFixed(1)} min`})}return o}
const CO=[{k:"plant",l:"Plant",g:"b"},{k:"shop",l:"Shop",g:"b"},{k:"line",l:"Line",g:"b"},{k:"station",l:"Station",g:"b"},{k:"workLocation",l:"Work Loc",g:"b"},
{k:"opTitle",l:"Operation Title",g:"o",w:1,e:"t"},{k:"name",l:"Name",g:"o"},{k:"description",l:"Description",g:"o",w:1,e:"t"},
{k:"opCategory",l:"Op Category",g:"o",e:"s",o:CA},{k:"parallelSetId",l:"Parallel SET ID",g:"o",e:"s",o:["—","PSET-01","PSET-02","PSET-03","PSET-04","PSET-05"]},
{k:"psetNumber",l:"Pset No.",g:"o",e:"t"},{k:"numShots",l:"Shots",g:"o",e:"t"},
{k:"sapConsumption",l:"SAP Cons.",g:"o",e:"s",o:["Yes","No"]},{k:"estimatedTime",l:"Est. Time",g:"o",e:"t"}];
const EK=CO.filter(c=>c.e).map(c=>c.k);
const DE=["Install cross member bracket","Apply structural adhesive","Torque bolts M10x1.5","Weld side panel inner","Inspect gap alignment","Load subassembly to fixture","Attach harness clips","Seal hem flange PVC","Install dash cross beam","Rivet quarter panel","Verify weld nugget","Mount fuel tank bracket"];
// =====================================================================
// STATE
// =====================================================================
let sP=null,sW=null,av=[],sr=[],aS={k:null,d:'a'},rS={k:null,d:'a'},aF={},rF={};
let mw=[],ov={};
// PCA Tracker state
let pcaRecords=[];
let pcaSort={k:'date',d:'d'};
let pcaFilterStatus='';
let currentDrawerRec=null;
let pcaSeq=0;
const $=id=>document.getElementById(id);
function toast(m,type){
const t=$('toast');
$('toastMsg').textContent=m;
t.className='toast'+(type==='ok'?' ok':type==='info'?' info':'')+' show';
clearTimeout(t._t);
t._t=setTimeout(()=>t.classList.remove('show'),3000);
}
// =====================================================================
// SEED DATA
// =====================================================================
function seedPCA(){
const statuses=['Pending Approval','Pending Approval','Approved','Rejected','Pending Approval'];
const approvers=['James Whitfield (BOP Owner)','Sarah Patel (Manufacturing Mgr)','David Chen (Process Engineer Lead)','Claire Houghton (Plant ME Lead)','Robert Osei (Quality Assurance)'];
const plants=['Castle Bromwich Assembly','Solihull Manufacturing','Halewood Body & Assembly','Solihull Manufacturing','Castle Bromwich Assembly'];
const wls=['BIW Framing Station A','General Assembly Line 1','Weld Line A','General Assembly Line 2','Trim & Final Line 1'];
const daysAgo=[5,3,8,12,1];
const changes=[
[{op:'GOP_WL_CB_BIW_01_001',attr:'estimatedTime',old:'1.2 min',nv:'1.5 min'},{op:'GOP_WL_CB_BIW_01_002',attr:'numShots',old:'1',nv:'2'}],
[{op:'GOP_WL_SH_GA_01_001',attr:'opCategory',old:'Assembly',nv:'Fastening'},{op:'GOP_WL_SH_GA_01_003',attr:'sapConsumption',old:'No',nv:'Yes'},{op:'GOP_WL_SH_GA_01_005',attr:'estimatedTime',old:'2.0 min',nv:'2.4 min'}],
[{op:'GOP_WL_HW_WD_01_002',attr:'parallelSetId',old:'—',nv:'PSET-01'},{op:'GOP_WL_HW_WD_01_004',attr:'numShots',old:'2',nv:'3'}],
[{op:'GOP_WL_SH_GA_02_001',attr:'description',old:'Install dash cross beam',nv:'Install dash cross beam reinforced'}],
[{op:'GOP_WL_CB_TR_01_001',attr:'estimatedTime',old:'1.8 min',nv:'2.0 min'}],
];
for(let i=0;i<5;i++){
const d=new Date();d.setDate(d.getDate()-daysAgo[i]);
const dd=new Date();dd.setDate(dd.getDate()+(7-daysAgo[i]));
const status=statuses[i];
const rec={
ref:`PCA-2024-${String(100+i+1).padStart(3,'0')}`,
date:d.toISOString(),
initiator:'ME Engineer',
plant:plants[i],
workLocation:wls[i],
approver:approvers[i],
informed:'ME Team – '+plants[i].split(' ')[0],
severity:'Medium',
dueDate:dd.toISOString().split('T')[0],
status:status,
changes:changes[i],
comment:'Please review the attached operations for attribute updates.',
timeline:bTimeline(status,approvers[i],d),
};
pcaRecords.push(rec);
}
}
function bTimeline(status,approver,initiatedDate){
const steps=[
{who:'ME Engineer',role:'Initiator',ts:initiatedDate.toLocaleString(),state:'done',comment:'Change initiated via MECA widget.'}
];
const apTs=new Date(initiatedDate);apTs.setHours(apTs.getHours()+2);
if(status==='Approved'){
steps.push({who:'System',role:'Route',ts:apTs.toLocaleString(),state:'done',comment:'Approval Route created and assigned.'});
const apTs2=new Date(apTs);apTs2.setDate(apTs2.getDate()+1);
steps.push({who:approver.split(' (')[0],role:approver.match(/\(([^)]+)\)/)?.[1]||'Approver',ts:apTs2.toLocaleString(),state:'done',comment:'Approved. Changes are clear and within process tolerance.'});
steps.push({who:'DELMIA System',role:'Auto-Update',ts:new Date(apTs2.getTime()+60000).toLocaleString(),state:'done',comment:'BOP attributes updated in DELMIA.'});
} else if(status==='Rejected'){
steps.push({who:'System',role:'Route',ts:apTs.toLocaleString(),state:'done',comment:'Approval Route created and assigned.'});
const apTs2=new Date(apTs);apTs2.setDate(apTs2.getDate()+1);
steps.push({who:approver.split(' (')[0],role:approver.match(/\(([^)]+)\)/)?.[1]||'Approver',ts:apTs2.toLocaleString(),state:'rejected',comment:'Rejected. Estimated time increase needs sign-off from Plant Director first.'});
} else {
steps.push({who:'System',role:'Route',ts:apTs.toLocaleString(),state:'done',comment:'Approval Route created. Awaiting approver action.'});
steps.push({who:approver.split(' (')[0],role:approver.match(/\(([^)]+)\)/)?.[1]||'Approver',ts:null,state:'active',comment:null});
}
return steps;
}
// =====================================================================
// TABS
// =====================================================================
function sTab(t){
$('tabM').classList.toggle('on',t==='m');
$('tabM').classList.toggle('active',t==='m');
$('tabB').classList.toggle('on',t==='b');
$('tabB').classList.toggle('active',t==='b');
$('tabP').classList.toggle('on',t==='p');
$('tabP').classList.toggle('active',t==='p');
$('scrM').classList.toggle('active',t==='m');
$('scrBC').classList.toggle('active',t==='b');
$('scrMW').classList.remove('active');
$('scrP').classList.toggle('active',t==='p');
if(t==='m'){$('scrBC').classList.remove('active')}
if(t==='p'){rPCAKPI();rPCATable();closeDrawer()}
syncTabsState();
}
function syncTabsState(){
const inTile=$('scrMW').classList.contains('active');
$('tabsBar').classList.toggle('locked',inTile);
const showShortcut=$('tabB').classList.contains('active')&&!inTile;
$('btnPcaShortcut').style.display=showShortcut?'inline-flex':'none';
}
function openPCAShortcut(){sTab('p')}
function selCat(){$('scrBC').classList.remove('active');$('scrMW').classList.add('active');rMW();syncTabsState()}
function goBack(){
if(mw.length>0&&hC()){
sCf("Unsaved changes will be lost. Go back?",()=>{mw=[];ov={};$('scrMW').classList.remove('active');$('scrBC').classList.add('active');rMW();syncTabsState()});
}else{mw=[];ov={};$('scrMW').classList.remove('active');$('scrBC').classList.add('active');rMW();syncTabsState()}
}
// =====================================================================
// SEARCH MODAL
// =====================================================================
function openS(){$('sM').classList.add('open')}
function closeS(){$('sM').classList.remove('open');av=[];sr=[];sP=null;sW=null;$('iP').value='';$('iW').value='';$('iW').disabled=true;$('ldB').disabled=true;$('sTg').innerHTML='';rAT();rST()}
function sTA(ii,si,ci,df,os,dif){
const inp=$(ii),sg=$(si),cl=$(ci);
function sh(){const v=inp.value.toLowerCase(),d=df(),f=v?d.filter(x=>x.c.toLowerCase().includes(v)||x.l.toLowerCase().includes(v)):d;sg.innerHTML='';if(!f.length){sg.innerHTML='<div style="padding:8px;color:var(--3dx-gray-400);font-size:10px;text-align:center">No matches</div>';sg.classList.add('open');return}f.forEach(x=>{const di=document.createElement('div');di.className='suggestion-item';di.innerHTML=`<span class="suggestion-code">${x.c}</span><span class="suggestion-label">${x.l}</span>`;di.onclick=()=>{inp.value=x.c;sg.classList.remove('open');os(x);uC(inp,cl)};sg.appendChild(di)});sg.classList.add('open')}
inp.addEventListener('input',()=>{if(dif&&dif())return;sh();uC(inp,cl)});
inp.addEventListener('focus',()=>{if(dif&&dif())return;sh()});
cl.addEventListener('click',()=>{inp.value='';sg.classList.remove('open');uC(inp,cl);os(null)});
document.addEventListener('click',e=>{if(!e.target.closest('#'+ii)&&!e.target.closest('#'+si))sg.classList.remove('open')});
}
function uC(i,c){c.style.display=i.value?'flex':'none'}
function uSB(){$('ldB').disabled=!(sP&&sW)}
function uTg(){const c=$('sTg');c.innerHTML='';if(sP)c.innerHTML+=`<span class="search-tag">${sP.c}<button onclick="cPl()">×</button></span>`;if(sW)c.innerHTML+=`<span class="search-tag">${sW.c}<button onclick="cWl()">×</button></span>`}
function cPl(){$('iP').value='';sP=null;sW=null;$('iW').value='';$('iW').disabled=true;av=[];rAT();uSB();uTg()}
function cWl(){$('iW').value='';sW=null;uSB();uTg()}
sTA('iP','sgP','cP',()=>PL,x=>{sP=x;sW=null;$('iW').value='';$('iW').disabled=!x;uSB();uTg()},()=>false);
sTA('iW','sgW','cW',()=>sP?WL[sP.c]||[]:[],x=>{sW=x;uSB();uTg()},()=>!sP);
function ldOps(){if(!sP||!sW)return;av=gOps(sP.c,sW.c);const mi=new Set(mw.map(o=>o.id)),si=new Set(sr.map(o=>o.id));av=av.filter(o=>!mi.has(o.id)&&!si.has(o.id));aS={k:null,d:'a'};aF={};rAT()}
// =====================================================================
// SEARCH TABLES
// =====================================================================
function bH(hid,ss){
const th=$(hid);
let h='<tr><th class="col-check"><div class="th-content"><input type="checkbox" class="sa"></div></th>';
CO.forEach((c,i)=>{
const gs=i===0||CO[i-1].g!==c.g;
const cls=(c.w?'col-wide ':'')+(gs?'group-start ':'')+(ss.k===c.k?'sortable':'');
const ar=ss.k===c.k?(ss.d==='a'?'▲':'▼'):'▲';
h+=`<th class="${cls}" data-key="${c.k}">
<div class="th-content">
<div class="th-label" onclick="hS('${hid}','${c.k}')">${c.l} <span class="th-sort">${ar}</span></div>
<div class="th-filter"><input placeholder="Filter..." oninput="hF('${hid}','${c.k}',this.value)" onclick="event.stopPropagation()"></div>
</div>
<div class="th-resize"></div>
</th>`;
});
h+='</tr>';
th.innerHTML=h;
th.querySelector('.sa').onchange=function(){
const bid=hid.replace('TH','TB');
$(bid).querySelectorAll('input[type=checkbox]').forEach(cb=>{
cb.checked=this.checked;
cb.closest('tr').classList.toggle('selected',this.checked)
});
uXF()
};
initResize(th);
}
function initResize(th){
const resizes=th.querySelectorAll('.th-resize');
resizes.forEach(resize=>{
resize.addEventListener('mousedown',function(e){
e.preventDefault();
const th=this.parentElement;
const startX=e.clientX;
const startWidth=th.offsetWidth;
document.addEventListener('mousemove',resizeMove);
document.addEventListener('mouseup',resizeUp);
function resizeMove(ev){
const newWidth=startWidth+(ev.clientX-startX);
th.style.width=newWidth+'px';
const key=th.dataset.key;
const colIdx=[...th.parentElement.children].indexOf(th);
const tbody=th.parentElement.parentElement.querySelector('tbody');
if(tbody){
[...tbody.rows].forEach(row=>{
if(row.cells[colIdx]){
row.cells[colIdx].style.width=newWidth+'px';
}
});
}
}
function resizeUp(){
document.removeEventListener('mousemove',resizeMove);
document.removeEventListener('mouseup',resizeUp);
}
});
});
}
function bB(bid,data,ss,fs){const tb=$(bid);let f=[...data];Object.entries(fs).forEach(([k,v])=>{if(v){const lv=v.toLowerCase();f=f.filter(r=>String(r[k]).toLowerCase().includes(lv))}});if(ss.k)f.sort((a,b)=>{const av=String(a[ss.k]).toLowerCase(),bv=String(b[ss.k]).toLowerCase();return ss.d==='a'?av.localeCompare(bv):bv.localeCompare(av)});
tb.innerHTML='';f.forEach(r=>{const tr=document.createElement('tr');tr.dataset.id=r.id;let h=`<td class="col-check"><input type="checkbox" data-id="${r.id}"></td>`;CO.forEach((c,i)=>{const gs=i===0||CO[i-1].g!==c.g;h+=`<td class="${(c.w?'col-wide ':'')+(gs?'group-start':'')}" title="${r[c.k]}">${r[c.k]}</td>`});tr.innerHTML=h;tr.onclick=e=>{if(e.target.type==='checkbox')return;const cb=tr.querySelector('input[type=checkbox]');cb.checked=!cb.checked;tr.classList.toggle('selected',cb.checked);uXF()};tr.querySelector('input[type=checkbox]').onchange=function(){tr.classList.toggle('selected',this.checked);uXF()};tb.appendChild(tr)})}
function rAT(){const t=$('aT'),em=$('aE');if(!av.length){t.style.display='none';em.style.display='flex'}else{t.style.display='';em.style.display='none';bH('aTH',aS);bB('aTB',av,aS,aF)};$('aC').textContent=av.length;uXF()}
function rST(){const t=$('sT'),em=$('sE');if(!sr.length){t.style.display='none';em.style.display='flex'}else{t.style.display='';em.style.display='none';bH('sTH',rS);bB('sTB',sr,rS,rF)};$('sC').textContent=sr.length;uXF()}
window.hS=function(hid,k){const ia=hid==='aTH';const s=ia?aS:rS;s.k===k?s.d=s.d==='a'?'d':'a':(s.k=k,s.d='a');ia?rAT():rST()};
window.hF=function(hid,k,v){const ia=hid==='aTH';(ia?aF:rF)[k]=v;bB(hid.replace('TH','TB'),ia?av:sr,ia?aS:rS,ia?aF:rF)};
function gCk(bid){return[...$(`${bid}`).querySelectorAll('input[type=checkbox]:checked')].map(cb=>cb.dataset.id)}
function uXF(){$('xA').disabled=!($('aTB').querySelectorAll('input[type=checkbox]:checked').length);$('xR').disabled=!($('sTB').querySelectorAll('input[type=checkbox]:checked').length)}
function xR(){const ids=gCk('aTB');if(!ids.length)return;sr=[...sr,...av.filter(o=>ids.includes(o.id))];av=av.filter(o=>!ids.includes(o.id));rAT();rST()}
function xL(){const ids=gCk('sTB');if(!ids.length)return;av=[...av,...sr.filter(o=>ids.includes(o.id))];sr=sr.filter(o=>!ids.includes(o.id));rAT();rST()}
function prS(){if(!sr.length){toast("At least one operation should be selected to proceed.");return}sr.forEach(o=>{if(!mw.find(m=>m.id===o.id)){mw.push(JSON.parse(JSON.stringify(o)));if(!ov[o.id]){ov[o.id]={};EK.forEach(k=>ov[o.id][k]=o[k])}}});sr=[];closeS();rMW();toast('Operations loaded.','ok')}
// =====================================================================
// MAIN WORKING WINDOW
// =====================================================================
function rMW(){
const h=mw.length>0;
$('dz').style.display=h?'none':'';
$('mwTW').style.display=h?'':'none';
$('caS').style.display=h?'':'none';
$('aBar').style.display=h?'':'none';
$('routeBanner').style.display=h?'':'none';
$('bOS').style.display=h?'none':'';
$('bAM').style.display=h?'':'none';
$('bCancelDock').style.display=h?'none':'flex';
$('s1').style.display=h?'':'none';
$('bBD').style.display=h?'':'none';
$('mwCnt').style.display=h?'':'none';
$('mwCnt').textContent=`${mw.length} operation(s)`;
if(h){rMWT();}
syncTabsState();
}
function rMWT(){
const th=$('mwTH'),tb=$('mwTBD');
let h='<tr><th class="col-check"><div class="th-content"><input type="checkbox" id="mSA"></div></th>';
CO.forEach((c,i)=>{
const gs=i===0||CO[i-1].g!==c.g;
const sorted=mwSort.k===c.k;
const ar=sorted?(mwSort.d==='a'?'▲':'▼'):'▲';
const w=mwColWidths[c.k];
const ws=w?` style="width:${w}px;min-width:${w}px;max-width:${w}px"`:'';
const fv=String(mwFilters[c.k]||'').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<');
h+=`<th class="${(c.w?'col-wide ' : '')+(gs?'group-start ' : '')+(sorted?'sortable' : '')}" data-key="${c.k}"${ws}>
<div class="th-content">
<div class="th-label" onclick="hSMW('${c.k}')">${c.l} <span class="th-sort">${ar}</span></div>
<div class="th-filter"><input placeholder="Filter..." value="${fv}" oninput="hFMW('${c.k}',this.value)" onclick="event.stopPropagation()"></div>
</div>
<div class="th-resize"></div>
</th>`;
});
h+='<th class="col-action"></th></tr>';
th.innerHTML=h;
$('mSA').onchange=function(){tb.querySelectorAll('input[type=checkbox]').forEach(cb=>{cb.checked=this.checked;cb.closest('tr').classList.toggle('selected',this.checked)})};
initResizeMW(th);
rMWTB();
}
let mwFilters={};
let mwSort={k:null,d:'a'};
let mwColWidths={};
window.hSMW=function(k){
if(mwSort.k===k){mwSort.d=mwSort.d==='a'?'d':'a'}
else{mwSort.k=k;mwSort.d='a'}
rMWT();
};
window.hFMW=function(k,v){
mwFilters[k]=v;
rMWTB();
};
function rMWTB(){
const tb=$('mwTBD');
let f=[...mw];
Object.entries(mwFilters).forEach(([k,v])=>{
if(v){
const lv=v.toLowerCase();
f=f.filter(r=>String(r[k]).toLowerCase().includes(lv));
}
});
if(mwSort.k){
const k=mwSort.k,d=mwSort.d;
f.sort((a,b)=>{
const av=String(a[k]).toLowerCase();
const bv=String(b[k]).toLowerCase();
const c=av.localeCompare(bv,undefined,{numeric:true,sensitivity:'base'});
return d==='a'?c:-c;
});
}
const idxById=new Map(mw.map((r,i)=>[r.id,i]));
tb.innerHTML='';
f.forEach(r=>{
const ri=idxById.get(r.id);
const tr=document.createElement('tr');tr.dataset.id=r.id;
let html=`<td class="col-check"><input type="checkbox" data-id="${r.id}"></td>`;
CO.forEach((c,i)=>{
const gs=i===0||CO[i-1].g!==c.g;const ie=!!c.e;
const ic=ov[r.id]&&ov[r.id][c.k]!==undefined&&ov[r.id][c.k]!==r[c.k];
const cls=(c.w?'col-wide ':'')+(gs?'group-start ':'')+(ie?'editable ':'')+(ic?'changed':'');
const w=mwColWidths[c.k];
const ws=w?` style="width:${w}px;min-width:${w}px;max-width:${w}px"`:'';
html+=`<td class="${cls}" title="${r[c.k]}" data-k="${c.k}" data-i="${ri}"${ws}>${r[c.k]}</td>`;
});
html+=`<td class="col-action"><button class="remove-btn" title="Remove" onclick="rmR('${r.id}')">-</button></td>`;
tr.innerHTML=html;
tr.querySelector('input[type=checkbox]').onchange=function(){tr.classList.toggle('selected',this.checked)};
tr.querySelectorAll('td.editable').forEach(td=>{td.addEventListener('dblclick',()=>sEd(td))});
tb.appendChild(tr);
});
}
function initResizeMW(th){
const resizes=th.querySelectorAll('.th-resize');
resizes.forEach(resize=>{
resize.addEventListener('mousedown',function(e){
e.preventDefault();
const th=this.parentElement;
const startX=e.clientX;
const startWidth=th.offsetWidth;
document.addEventListener('mousemove',resizeMove);
document.addEventListener('mouseup',resizeUp);
function resizeMove(ev){
const newWidth=Math.max(90,startWidth+(ev.clientX-startX));
th.style.width=newWidth+'px';
th.style.minWidth=newWidth+'px';
th.style.maxWidth=newWidth+'px';
const colIdx=[...th.parentElement.children].indexOf(th);
const key=th.dataset.key;
const tbody=th.parentElement.parentElement.querySelector('tbody');
if(tbody){
[...tbody.rows].forEach(row=>{
if(row.cells[colIdx]){
row.cells[colIdx].style.width=newWidth+'px';
row.cells[colIdx].style.minWidth=newWidth+'px';
row.cells[colIdx].style.maxWidth=newWidth+'px';
}
});
}
if(key)mwColWidths[key]=newWidth;
}
function resizeUp(){
document.removeEventListener('mousemove',resizeMove);
document.removeEventListener('mouseup',resizeUp);
}
});
});
}
function sEd(td){
if(td.querySelector('input,select'))return;
const k=td.dataset.k,idx=+td.dataset.i,col=CO.find(c=>c.k===k),val=mw[idx][k];
const isNumeric=k==='numShots'||k==='psetNumber'||k==='estimatedTime';
if(col.e==='t'){
const inp=document.createElement('input');
inp.type=isNumeric?'number':'text';
if(k==='estimatedTime'){inp.type='text';inp.placeholder='e.g., 1.5 min'}
inp.value=isNumeric&&k!=='estimatedTime'?val.replace(/[^0-9.-]/g,''):val;
td.textContent='';td.appendChild(inp);inp.focus();inp.select();
const cm=()=>{
let v=inp.value.trim();
if(isNumeric&&k!=='estimatedTime'){v=v.replace(/[^0-9.-]/g,'')}
mw[idx][k]=v||val;rMWTB()
};
inp.onblur=cm;
inp.onkeydown=e=>{if(e.key==='Enter')cm();if(e.key==='Escape'){mw[idx][k]=val;rMWTB()}};
inp.oninput=e=>{if(isNumeric&&k!=='estimatedTime')e.target.value=e.target.value.replace(/[^0-9.-]/g,'')}}
else if(col.e==='s'){const sel=document.createElement('select');(col.o||[]).forEach(o=>{const op=document.createElement('option');op.value=o;op.textContent=o;if(o===val)op.selected=true;sel.appendChild(op)});td.textContent='';td.appendChild(sel);sel.focus();sel.onchange=()=>{mw[idx][k]=sel.value;rMWTB()};sel.onblur=()=>{mw[idx][k]=sel.value;rMWTB()}}
}
function rmR(id){mw=mw.filter(o=>o.id!==id);delete ov[id];rMW()}
function bDel(){const cbs=[...$('mwTBD').querySelectorAll('input[type=checkbox]:checked')];if(!cbs.length){toast("At least one operation must be selected to remove.");return}const ids=cbs.map(cb=>cb.dataset.id);mw=mw.filter(o=>!ids.includes(o.id));ids.forEach(id=>delete ov[id]);rMW();toast(`${ids.length} removed.`,'ok')}
function simDrop(){const pc=PL[Math.floor(Math.random()*PL.length)],wls=WL[pc.c],wl=wls[Math.floor(Math.random()*wls.length)];const ops=gOps(pc.c,wl.c).slice(0,3);ops.forEach(o=>{if(!mw.find(m=>m.id===o.id)){mw.push(o);ov[o.id]={};EK.forEach(k=>ov[o.id][k]=o[k])}});rMW();toast(`${ops.length} operations loaded via drag & drop.`,'ok')}
function hC(){for(const op of mw){const o=ov[op.id];if(!o)continue;for(const k of EK){if(o[k]!==op[k])return true}}return false}
function gC(){const ch=[];mw.forEach(op=>{const o=ov[op.id];if(!o)return;const c={};let a=false;EK.forEach(k=>{if(o[k]!==op[k]){c[k]={o:o[k],n:op[k]};a=true}});if(a)ch.push({op,ch:c})});return ch}
function doInit(){
const ch=gC();
if(!ch.length){toast("At least one attribute change is required before initiating.");return}
const ap=$('caAp').value;
if(!ap){toast("Please select an Approver before initiating.");$('caAp').focus();return}
shSm(ch);
}
function shSm(ch){
const wls=[...new Set(mw.map(o=>o.workLocation))];
let h=`<h4>Change Details</h4>`;
h+=`<div class="summary-row"><span class="summary-label">Title:</span><span class="summary-value">Operation Attribute Change — ${wls.join(', ')}</span></div>`;
h+=`<div class="summary-row"><span class="summary-label">Category:</span><span class="summary-value">BOP Operations Attribute Change</span></div>`;
h+=`<div class="summary-row"><span class="summary-label">Credentials:</span><span class="summary-value">Manufacturing Space – Jaguar Motors UK</span></div>`;
h+=`<div class="summary-row"><span class="summary-label">Severity:</span><span class="summary-value">${$('caSev').value}</span></div>`;
h+=`<div class="summary-row"><span class="summary-label">Approver:</span><span class="summary-value">${$('caAp').value}</span></div>`;
if($('caDD').value)h+=`<div class="summary-row"><span class="summary-label">Due Date:</span><span class="summary-value">${$('caDD').value}</span></div>`;
h+=`<h4 style="margin-top:16px">Operations & Changed Attributes</h4>`;
h+=`<table class="summary-table"><thead><tr><th>Plant</th><th>Work Location</th><th>Op Title</th><th>Name</th>`;
const ak=new Set();ch.forEach(c=>Object.keys(c.ch).forEach(k=>ak.add(k)));
ak.forEach(k=>{const col=CO.find(c=>c.k===k);h+=`<th>${col?col.l:k}</th>`});
h+=`</tr></thead><tbody>`;
ch.forEach(c=>{h+=`<tr><td>${c.op.plant}</td><td>${c.op.workLocation}</td><td>${c.op.opTitle}</td><td>${c.op.name}</td>`;ak.forEach(k=>{if(c.ch[k])h+=`<td class="change-highlight">${c.ch[k].o} → ${c.ch[k].n}</td>`;else h+=`<td>—</td>`});h+=`</tr>`});
h+=`</tbody></table>`;
$('smB').innerHTML=h;
$('smM').classList.add('open');
}
function clSm(){$('smM').classList.remove('open')}
// =====================================================================
// ROUTE CREATION MODAL
// =====================================================================
function openRouteModal(){
clSm();
const ch=gC();
const wls=[...new Set(mw.map(o=>o.workLocation))];
const ref=`PCA-2024-${String(101+pcaRecords.length).padStart(3,'0')}`;
$('rTitle').value=`Operation Attribute Change — ${wls.join(', ')}`;
$('rRef').value=ref;
$('rApprover').value=$('caAp').value;
$('rInformed').value=$('caIn').value||'—';
let rows='';
ch.forEach(c=>{
Object.entries(c.ch).forEach(([k,v])=>{
const col=CO.find(x=>x.k===k);
rows+=`<div class="rcl-row"><span class="rc-op">${c.op.name}</span><span class="rc-attr">${col?col.l:k}</span><span class="rc-old">${v.o}</span><span class="rc-new">${v.n}</span></div>`;
});
});
$('rChangeSummary').innerHTML=rows||'<div style="padding:8px;font-size:10px;color:var(--3dx-gray-400)">No changes captured.</div>';
$('rComment').value='';
$('routeModal').classList.add('open');
}
function closeRouteModal(){$('routeModal').classList.remove('open')}
function submitRoute(){
const ch=gC();
const wls=[...new Set(mw.map(o=>o.workLocation))];
const plants=[...new Set(mw.map(o=>o.plant))];
const ref=$('rRef').value;
const now=new Date();
const changes=[];
ch.forEach(c=>{Object.entries(c.ch).forEach(([k,v])=>{const col=CO.find(x=>x.k===k);changes.push({op:c.op.name,attr:col?col.l:k,old:v.o,nv:v.n})})});
const timeline=[
{who:'ME Engineer',role:'Initiator',ts:now.toLocaleString(),state:'done',comment:$('rComment').value||'Change initiated via MECA widget.'},
{who:'System',role:'Route',ts:new Date(now.getTime()+500).toLocaleString(),state:'done',comment:'Approval Route created. Approver task assigned.'},
{who:$('caAp').value.split(' (')[0],role:$('caAp').value.match(/\(([^)]+)\)/)?.[1]||'Approver',ts:null,state:'active',comment:null},
];
const rec={
ref,
date:now.toISOString(),
initiator:'ME Engineer',
plant:plants.join(', '),
workLocation:wls.join(', '),
approver:$('caAp').value,
informed:$('caIn').value||'—',
severity:$('caSev').value,
dueDate:$('caDD').value,
status:'Pending Approval',
changes,
comment:$('rComment').value,
timeline,
};
pcaRecords.unshift(rec);
closeRouteModal();
$('suRef').textContent=ref;
$('suO').classList.add('open');
uPCABadge();
}
function clSu(){$('suO').classList.remove('open');mw=[];ov={};$('scrMW').classList.remove('active');$('scrBC').classList.add('active');rMW()}
function clSuGoTracker(){$('suO').classList.remove('open');mw=[];ov={};$('scrMW').classList.remove('active');rMW();sTab('p')}
// =====================================================================
// CONFIRM / CANCEL
// =====================================================================
function sCf(m,y){$('cfMsg').textContent=m;$('cfD').classList.add('open');$('cfY').onclick=()=>{$('cfD').classList.remove('open');y()};$('cfN').onclick=()=>{$('cfD').classList.remove('open')}}
function cfCancel(){sCf("Are you sure you want to cancel? All unsaved changes will be lost.",()=>{mw=[];ov={};$('scrMW').classList.remove('active');$('scrBC').classList.add('active');rMW()})}
// =====================================================================
// PCA TRACKER
// =====================================================================
function rPCA(){pcaFilters={};rPCAKPI();rPCATable();toast('PCA Tracker refreshed.','info')}
function rPCAKPI(){
const all=pcaRecords.length;
const pending=pcaRecords.filter(r=>r.status==='Pending Approval').length;
const approved=pcaRecords.filter(r=>r.status==='Approved').length;
const rejected=pcaRecords.filter(r=>r.status==='Rejected').length;
const kpi=$('pcaKPI');
kpi.innerHTML=`
<div class="pca-kpi-card k-all ${pcaFilterStatus===''?'active':''}" onclick="filterKPI('')">
<div class="kpi-value">${all}</div><div class="kpi-label">Total PCA Changes</div>
</div>
<div class="pca-kpi-card k-pending ${pcaFilterStatus==='Pending Approval'?'active':''}" onclick="filterKPI('Pending Approval')">
<div class="kpi-value">${pending}</div><div class="kpi-label">Pending Approval</div>
</div>
<div class="pca-kpi-card k-approved ${pcaFilterStatus==='Approved'?'active':''}" onclick="filterKPI('Approved')">
<div class="kpi-value">${approved}</div><div class="kpi-label">Approved</div>
</div>
<div class="pca-kpi-card k-rejected ${pcaFilterStatus==='Rejected'?'active':''}" onclick="filterKPI('Rejected')">
<div class="kpi-value">${rejected}</div><div class="kpi-label">Rejected</div>
</div>`;
uPCABadge();
}
function filterKPI(s){
pcaFilters={};
pcaFilterStatus=s;
$('pcaStatusF').value=s;
rPCAKPI();
rPCATable();
}
function uPCABadge(){
const p=pcaRecords.filter(r=>r.status==='Pending Approval').length;
const b=$('pcaBadge');
if(p>0){b.style.display='';b.textContent=p}else{b.style.display='none'}
}
let pcaFilters={};
window.filterPCA=function(k,v){
pcaFilters[k]=v;
rPCATable();
};
function rPCATable(){
const search=$('pcaSearch').value.toLowerCase();
const plantF=$('pcaPlantF').value;
const statusF=$('pcaStatusF').value||pcaFilterStatus;
let data=[...pcaRecords];
if(search)data=data.filter(r=>r.ref.toLowerCase().includes(search)||r.plant.toLowerCase().includes(search)||r.approver.toLowerCase().includes(search)||r.initiator.toLowerCase().includes(search)||r.workLocation.toLowerCase().includes(search));
if(plantF)data=data.filter(r=>r.plant.includes(plantF));
if(statusF)data=data.filter(r=>r.status===statusF);
// Apply column filters
Object.entries(pcaFilters).forEach(([k,v])=>{
if(v){
const lv=v.toLowerCase();
if(k==='ops'){
data=data.filter(r=>String(r.changes.length).toLowerCase().includes(lv));
}else if(k==='attrs'){
const attrs=[...new Set(r.changes.map(c=>c.attr))].join(', ');
data=data.filter(r=>attrs.toLowerCase().includes(lv));
}else if(k==='date'){
const ds=new Date(r.date).toLocaleDateString('en-GB').toLowerCase();
data=data.filter(r=>ds.includes(lv));
}else if(k==='dueDate'){
const ds=r.dueDate?new Date(r.dueDate).toLocaleDateString('en-GB').toLowerCase():'';
data=data.filter(r=>ds.includes(lv));
}else{
data=data.filter(r=>String(r[k]).toLowerCase().includes(lv));
}
}
});
data.sort((a,b)=>{
let av=a[pcaSort.k]||'',bv=b[pcaSort.k]||'';
if(pcaSort.k==='ops')av=String(a.changes.length),bv=String(b.changes.length);
if(pcaSort.k==='attrs'){const ua=new Set(a.changes.map(c=>c.attr));const ub=new Set(b.changes.map(c=>c.attr));av=String(ua.size);bv=String(ub.size)}
return pcaSort.d==='a'?av.localeCompare(bv):bv.localeCompare(av);
});
$('pcaRowCount').textContent=`${data.length} record${data.length!==1?'s':''}`;
const tb=$('pcaTBody'),tbl=$('pcaTable'),em=$('pcaEmpty');
if(!data.length){tbl.style.display='none';em.style.display='flex';return}
tbl.style.display='';em.style.display='none';
tb.innerHTML='';
data.forEach(r=>{
const tr=document.createElement('tr');
const opsCount=r.changes.length;
const attrsUniq=[...new Set(r.changes.map(c=>c.attr))].join(', ');
const badgeCls=r.status==='Approved'?'badge-approved':r.status==='Rejected'?'badge-rejected':r.status==='Pending Approval'?'badge-pending':'badge-draft';
const dateStr=new Date(r.date).toLocaleDateString('en-GB',{day:'2-digit',month:'short',year:'numeric'});
const due=r.dueDate?new Date(r.dueDate).toLocaleDateString('en-GB',{day:'2-digit',month:'short'}):'—';
tr.innerHTML=`
<td style="font-weight:600;color:var(--3dx-blue);font-size:10.5px">${r.ref}</td>
<td>${dateStr}</td>
<td>${r.initiator}</td>
<td>${r.plant}</td>
<td style="text-align:center;font-weight:600">${opsCount}</td>
<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis" title="${attrsUniq}">${attrsUniq}</td>
<td>${r.approver}</td>
<td>${due}</td>
<td><span class="badge ${badgeCls}">${r.status}</span></td>`;
tr.onclick=()=>openDrawer(r);
tb.appendChild(tr);
});
initPCAResize();
}
function initPCAResize(){
const th=$('pcaTHEAD');
if(!th)return;
const resizes=th.querySelectorAll('.th-resize');
resizes.forEach(resize=>{
resize.addEventListener('mousedown',function(e){
e.preventDefault();
const th=this.parentElement;
const startX=e.clientX;
const startWidth=th.offsetWidth;
document.addEventListener('mousemove',resizeMove);
document.addEventListener('mouseup',resizeUp);
function resizeMove(ev){
const newWidth=startWidth+(ev.clientX-startX);
th.style.width=newWidth+'px';
const colIdx=[...th.parentElement.children].indexOf(th);
const tbody=th.parentElement.parentElement.querySelector('tbody');
if(tbody){
[...tbody.rows].forEach(row=>{
if(row.cells[colIdx]){
row.cells[colIdx].style.width=newWidth+'px';
}
});
}
}
function resizeUp(){
document.removeEventListener('mousemove',resizeMove);
document.removeEventListener('mouseup',resizeUp);
}
});
});
}
function sortPCA(k){
['ref','date','initiator','plant','ops','attrs','approver','dueDate','status'].forEach(key=>{const el=$('sh'+key);if(el)el.textContent=''});
if(pcaSort.k===k){pcaSort.d=pcaSort.d==='a'?'d':'a'}else{pcaSort.k=k;pcaSort.d='a'}
const el=$('sh'+k);if(el)el.textContent=pcaSort.d==='a'?'▲':'▼';
rPCATable();
}
function exportCSV(){
if(!pcaRecords.length){toast("No records to export.");return}
const headers=['PCA Ref','Date','Initiated By','Plant','Work Location','Operations Changed','Attributes Modified','Approver','Due Date','Status'];
const rows=pcaRecords.map(r=>[
r.ref,
new Date(r.date).toLocaleDateString(),
r.initiator,
r.plant,
r.workLocation,
r.changes.length,
[...new Set(r.changes.map(c=>c.attr))].join('; '),
r.approver,
r.dueDate||'',
r.status
]);
const csv=[headers,...rows].map(r=>r.map(v=>`"${String(v).replace(/"/g,'""')}"`).join(',')).join('\n');
const a=document.createElement('a');a.href='data:text/csv;charset=utf-8,'+encodeURIComponent(csv);a.download=`MECA_PCA_Tracker_${new Date().toISOString().split('T')[0]}.csv`;a.click();
toast('Export downloaded.','ok');
}
// =====================================================================
// PCA DETAIL DRAWER
// =====================================================================
function openDrawer(r){
currentDrawerRec=r;
$('drawerTitle').textContent=r.ref+' — Detail';
let chRows=r.changes.map(c=>`<tr><td>${c.op}</td><td>${c.attr}</td><td class="old-value">${c.old}</td><td class="new-value">${c.nv}</td></tr>`).join('');
let tlHtml=r.timeline.map((s,i)=>{
const dotCls=s.state==='done'?'done':s.state==='active'?'active':s.state==='rejected'?'rejected':'wait';
const icon=s.state==='done'?'✓':s.state==='active'?'…':s.state==='rejected'?'✕':String(i+1);
return `<div class="timeline-step">
<div class="timeline-dot ${dotCls}">${icon}</div>
<div class="timeline-info">
<div class="ti-who">${s.who}</div>
<div class="ti-role">${s.role}</div>
${s.ts?`<div class="ti-ts">${s.ts}</div>`:'<div class="ti-ts" style="color:var(--3dx-warning)">Awaiting action...</div>'}
${s.comment?`<div class="ti-comment">${s.comment}</div>`:''}
</div>
</div>`;
}).join('');
const badgeCls=r.status==='Approved'?'badge-approved':r.status==='Rejected'?'badge-rejected':r.status==='Pending Approval'?'badge-pending':'badge-draft';
$('drawerBody').innerHTML=`
<div class="drawer-section">
<h5><svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg> PCA Record</h5>
<div class="drawer-row"><span class="drawer-label">Reference</span><span class="drawer-value" style="font-weight:700;color:var(--3dx-blue)">${r.ref}</span></div>
<div class="drawer-row"><span class="drawer-label">Status</span><span class="drawer-value"><span class="badge ${badgeCls}">${r.status}</span></span></div>
<div class="drawer-row"><span class="drawer-label">Initiated</span><span class="drawer-value">${new Date(r.date).toLocaleString()}</span></div>
<div class="drawer-row"><span class="drawer-label">Initiated By</span><span class="drawer-value">${r.initiator}</span></div>
<div class="drawer-row"><span class="drawer-label">Plant</span><span class="drawer-value">${r.plant}</span></div>
<div class="drawer-row"><span class="drawer-label">Work Location</span><span class="drawer-value">${r.workLocation}</span></div>
<div class="drawer-row"><span class="drawer-label">Approver</span><span class="drawer-value">${r.approver}</span></div>
<div class="drawer-row"><span class="drawer-label">Informed</span><span class="drawer-value">${r.informed}</span></div>
<div class="drawer-row"><span class="drawer-label">Severity</span><span class="drawer-value">${r.severity}</span></div>
<div class="drawer-row"><span class="drawer-label">Due Date</span><span class="drawer-value">${r.dueDate||'—'}</span></div>
</div>
<div class="drawer-section">
<h5><svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> Changed Attributes (${r.changes.length})</h5>
<table class="drawer-change-table">
<thead><tr><th>Operation</th><th>Attribute</th><th>Previous</th><th>New</th></tr></thead>
<tbody>${chRows}</tbody>
</table>
</div>
<div class="drawer-section">
<h5><svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> Approval Route Timeline</h5>
<div class="route-timeline">${tlHtml}</div>
</div>
`;
const acts=$('drawerActions');
if(r.status==='Pending Approval'){acts.style.display='flex';$('drawerComment').value=''}
else{acts.style.display='none'}
$('pcaDrawer').classList.add('open');
document.querySelectorAll('.pca-table tr').forEach(tr=>tr.classList.remove('pca-selected'));
const matchTr=[...$('pcaTBody').querySelectorAll('tr')].find(tr=>tr.querySelector('td')?.textContent===r.ref);
if(matchTr)matchTr.classList.add('pca-selected');
}
function closeDrawer(){$('pcaDrawer').classList.remove('open');currentDrawerRec=null;document.querySelectorAll('.pca-table tr').forEach(tr=>tr.classList.remove('pca-selected'))}
function actOnPCA(action){
if(!currentDrawerRec)return;
const comment=$('drawerComment').value.trim()||null;
const r=currentDrawerRec;
const ts=new Date().toLocaleString();
const approverName=r.approver.split(' (')[0];
const approverRole=r.approver.match(/\(([^)]+)\)/)?.[1]||'Approver';
if(action==='approve'){
r.status='Approved';
r.timeline.find(s=>s.state==='active').state='done';
const activeStep=r.timeline.find(s=>s.state==='active'&&!s.ts);
if(activeStep){activeStep.ts=ts;activeStep.comment=comment||'Approved. Changes can proceed.'}
else{const last=r.timeline[r.timeline.length-1];if(last.state==='active'){last.ts=ts;last.state='done';last.comment=comment||'Approved. Changes can proceed.'}}
r.timeline.push({who:'DELMIA System',role:'Auto-Update',ts:new Date().toLocaleString(),state:'done',comment:'BOP attributes updated in DELMIA.'});
toast(`${r.ref} Approved. BOP changes applied.`,'ok');
} else {
r.status='Rejected';
const last=r.timeline[r.timeline.length-1];
if(last.state==='active'){last.ts=ts;last.state='rejected';last.comment=comment||'Rejected.'}
toast(`${r.ref} Rejected.`);
}
closeDrawer();
rPCAKPI();
rPCATable();
}
function setDueDateMin(){
const dd=$('caDD');
if(!dd)return;
const t=new Date();
t.setHours(0,0,0,0);
dd.min=t.toISOString().split('T')[0];
}
// =====================================================================
// INIT
// =====================================================================
seedPCA();
setDueDateMin();
rMW();
uPCABadge();
</script>
</body>
</html>