/*! izy-table v1.9.1 — build 2026-10-15 — ghép từ src/css/*.css; ĐỪNG sửa file này */

/* ===== 00-tokens.css ===== */
/*!
 * izy-table.css — CSS của gói izy-table (ghép từ src/css/ bằng build.sh). Nạp SAU CSS theme.
 * Mọi màu đi qua var(--vz-*, fallback) nên ngoài Velzon vẫn ra màu hợp lý; dark mode qua
 * [data-bs-theme="dark"] chuẩn Bootstrap 5.3. Các token --izy-grid-* / --izy-st-* điều khiển màu bảng.
 *
 * Bảng màu LIGHT lấy theo lưới của MISA AMIS (đo từ CSS công khai của họ): nền tiêu đề xám trung
 * tính đậm hơn thân bảng một nấc rõ ràng, kẻ dọc #ced1d6 nhìn thấy được, chữ gần như đen (#10141b)
 * thay vì xám nhạt. Trước đây mọi nền đều là xanh-xám rất nhạt (#eaeff6/#edf1f6) nên tiêu đề, ô
 * ghim, dòng tổng và thân bảng "nhờ nhờ" giống nhau. DARK giữ nguyên như cũ.
 */
:root {
    /* Kẻ trong lòng bảng (nhạt) vs khung + kẻ dọc tiêu đề (rõ) — MISA dùng #ced1d6 cho khung. */
    --izy-grid-border: #dfe2e7;
    --izy-grid-frame: #ced1d6;
    /* Viền ô của <table> tách riêng khỏi --izy-grid-border: dark phải giữ nguyên #32383e của
       Velzon (yêu cầu: chỉ đổi light mode). */
    --izy-tbl-border: var(--izy-grid-border);
    --izy-tbl-foot-border: 1px solid var(--izy-grid-frame);
    /* Nền tiêu đề = xám trung tính pha 6% màu primary của theme (MISA cũng nhích nền tiêu đề theo
       màu thương hiệu). Dòng đầu là màu chốt cho trình duyệt không có color-mix; dòng sau ghi đè
       và tự chạy theo data-theme-colors (green/purple/blue) của Velzon. */
    --izy-grid-head-bg: #e3e5eb;
    --izy-grid-head-bg: color-mix(in srgb, var(--vz-primary, #405189) 6%, #edeef1);
    --izy-grid-filter-bg: #f2f3f5;
    --izy-grid-surface: var(--vz-secondary-bg, #fff);
    --izy-grid-shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 10px 26px -18px rgba(15, 23, 42, .25);
    /* Chữ: đậm hẳn để tương phản với nền trắng (MISA: #10141b / phụ #6b707a). */
    --izy-grid-text: #10141b;
    --izy-grid-head-text: #10141b;
    --izy-grid-muted: #6b707a;
    /* Ô ghim sẫm hơn thân bảng (trắng) một nấc để mắt tách được phần đứng yên với phần trượt —
       dark mode cũng lệch như vậy (#2c333c trên nền tối). Vùng cuộn giữ nguyên nền trắng. */
    --izy-st-bg: #f4f5f7;
    --izy-st-foot-bg: #eceef2;
    --izy-st-foot-bg: color-mix(in srgb, var(--vz-primary, #405189) 6%, #f7f8f9);
    --izy-st-head-bg: var(--izy-grid-head-bg);
    /* Hover/chọn dòng: sắc độ nhạt của primary, ĐỤC để ô ghim dùng lại được (nền trong suốt của
       Bootstrap sẽ để lộ phần bảng trượt bên dưới ô ghim). */
    --izy-row-hover-bg: #eef1f8;
    --izy-row-hover-bg: color-mix(in srgb, var(--vz-primary, #405189) 9%, #fff);
    --izy-row-active-bg: #dfe5f1;
    --izy-row-active-bg: color-mix(in srgb, var(--vz-primary, #405189) 18%, #fff);

    /* Màu trạng thái HÀNG (khối "Row state" bên dưới): mỗi trạng thái một cặp nền/hover,
       lệch nền bảng đúng một nấc để đọc ra ngay mà không thành vệt màu chói giữa lưới. */
    --izy-row-blocked-bg: #e5e8ec;
    --izy-row-blocked-hover: #d9dde3;
    --izy-row-danger-bg: #fbe7e5;
    --izy-row-danger-hover: #f5d6d3;
    --izy-row-warning-bg: #fdf1dc;
    --izy-row-warning-hover: #f9e4c2;
    --izy-row-success-bg: #e3f4ee;
    --izy-row-success-hover: #d1ece2;
}
[data-bs-theme="dark"],
[data-layout-mode="dark"] {
    --izy-grid-border: #363e47;
    --izy-grid-frame: #4a545f;
    --izy-grid-surface: #262c33;
    --izy-grid-head-bg: #313944;
    --izy-grid-filter-bg: #22272d;
    --izy-grid-shadow: 0 1px 2px rgba(0, 0, 0, .25), 0 10px 26px -16px rgba(0, 0, 0, .55);
    --izy-grid-text: var(--vz-body-color, #ced4da);
    --izy-grid-head-text: var(--vz-body-color, #ced4da);
    --izy-grid-muted: #98a6ad;
    --izy-tbl-border: #32383e;
    --izy-tbl-foot-border: 0 none;
    /* Dark mode: tăng độ sáng nhẹ so với content #212529/#262c33 để vẫn thấy ranh giới. */
    --izy-st-bg: #2c333c;
    --izy-st-foot-bg: #323a45;
    --izy-st-head-bg: #38424e;
    /* = rgba(var(--vz-body-color-rgb), .04) phủ trên nền card #212529 mà Bootstrap đang render;
       để đục thì ô ghim mới dùng lại được, còn màu thì y hệt trước. */
    --izy-row-hover-bg: #282c30;
    --izy-row-active-bg: #282c30;
    /* Dark: vẫn "lệch nền bảng một nấc", nhưng lệch SÁNG lên thay vì tối xuống. */
    --izy-row-blocked-bg: #333a43;
    --izy-row-blocked-hover: #3d4550;
    --izy-row-danger-bg: #412b2c;
    --izy-row-danger-hover: #4d3335;
    --izy-row-warning-bg: #40382a;
    --izy-row-warning-hover: #4c4232;
    --izy-row-success-bg: #26382f;
    --izy-row-success-hover: #2e4539;
}

/* ---- Bảng izy đọc token thay cho mặc định Bootstrap ------------------------------------------
   Đặt trên chính <table> (không phải :root) để chỉ bảng trong .izy-scroll đổi màu, phần còn lại
   của trang giữ nguyên skin theme. Hàng có row-* khai lại các biến này ở <tr> nên vẫn thắng. */
.izy-scroll > table {
    --vz-table-color: var(--izy-grid-text);
    --vz-table-border-color: var(--izy-tbl-border);
    --vz-table-hover-bg: var(--izy-row-hover-bg);
    --vz-table-hover-color: var(--izy-grid-text);
    --vz-table-active-bg: var(--izy-row-active-bg);
    --vz-table-active-color: var(--izy-grid-text);
}
/* Velzon (app.min.css) khai `color: var(--vz-table-color-state),var(...)` — dấu phẩy thừa làm
   khai báo hỏng, nên ô <td> rơi về màu chữ của body. Tự đặt lại để token màu chữ có tác dụng. */
.izy-scroll > table > tbody > tr > * { color: var(--izy-grid-text); }
.izy-scroll > table > thead > tr > * {
    color: var(--izy-grid-head-text);
    font-weight: 600;
    background-color: var(--izy-grid-head-bg);
}
.izy-scroll > table > tfoot > tr > * {
    color: var(--izy-grid-head-text);
    font-weight: 600;
    border-top: var(--izy-tbl-foot-border);
}
/* Ô ghim tự vẽ nền ĐỤC nên hover của Bootstrap không chạm tới; cấp lại token cho cả dòng.
   Khối "Row state" bên dưới ghi đè lại được vì đặt SAU và cùng độ đặc hiệu. */
.table-hover > tbody > tr:hover { --izy-st-bg: var(--izy-row-hover-bg); }

/* ---- Row state: tô màu cả hàng theo trạng thái nghiệp vụ -------------------------------------
   Dùng: <tr class="row-blocked"> (hoặc row-danger / row-warning / row-success) trên bảng .table
   bất kỳ. KHÔNG tô thẳng `background` lên td: Bootstrap 5.3 vẽ ô bằng var(--vz-table-bg) +
   box-shadow inset, nên đặt lại token trên <tr> là cách duy nhất để zebra, hover và ô ghim cùng
   đổi màu theo. Chữ giữ var(--vz-table-color) nên đọc được ở cả light lẫn dark.
   Thêm trạng thái mới = thêm 1 cặp token ở :root/[data-bs-theme="dark"] + 2 dòng selector. */
tr.row-blocked { --izy-row-bg: var(--izy-row-blocked-bg); --izy-row-hover: var(--izy-row-blocked-hover); }
tr.row-danger  { --izy-row-bg: var(--izy-row-danger-bg);  --izy-row-hover: var(--izy-row-danger-hover); }
tr.row-warning { --izy-row-bg: var(--izy-row-warning-bg); --izy-row-hover: var(--izy-row-warning-hover); }
tr.row-success { --izy-row-bg: var(--izy-row-success-bg); --izy-row-hover: var(--izy-row-success-hover); }
tr.row-blocked, tr.row-danger, tr.row-warning, tr.row-success {
    --vz-table-bg: var(--izy-row-bg);
    --vz-table-striped-bg: var(--izy-row-bg);
    --vz-table-active-bg: var(--izy-row-hover);
    --vz-table-hover-bg: var(--izy-row-hover);
    /* Ô cột ghim tự vẽ nền ĐỤC để che phần bảng trượt bên dưới; nó đọc --izy-st-bg. */
    --izy-st-bg: var(--izy-row-bg);
}
.table-hover > tbody > tr.row-blocked:hover,
.table-hover > tbody > tr.row-danger:hover,
.table-hover > tbody > tr.row-warning:hover,
.table-hover > tbody > tr.row-success:hover { --izy-st-bg: var(--izy-row-hover); }

/* ---- Nền ô vẽ bằng background-color, KHÔNG qua lớp box-shadow của Bootstrap ---------------------
   Bootstrap 5.3 vẽ hover / zebra / chọn dòng bằng `box-shadow: inset 0 0 0 9999px` nằm Ở TRÊN
   background của ô, nên CSS riêng của trang (`.wf-approved { background: #00a65a !important }`) thắng
   về đặc hiệu mà vẫn bị phủ xám lúc rê chuột. Gói gộp 3 lớp của Bootstrap (state > type > bg) vào
   chính background-color: cùng đặc hiệu (0,1,1) với luật gốc, nạp sau nên thay nó. Hover / zebra /
   active trông y nguyên (--vz-table-bg của Velzon là transparent), còn ô có CSS riêng thì CSS riêng
   thắng như mọi thuộc tính khác — trang chỉ cần `!important` hoặc đặc hiệu > (0,1,1) (vd `#id td.x`),
   không phải gắn class gì thêm. Ô ghim vẫn đọc --izy-st-bg (core gán background theo chỉ số cột,
   đặc hiệu cao hơn). Tô CẢ HÀNG vẫn là row-* ở trên. */
.table > :not(caption) > * > * {
    box-shadow: none;
    background-color: var(--vz-table-bg-state, var(--vz-table-bg-type, var(--vz-table-bg)));
}

/* ===== 10-core.css ===== */
/* ---- Core: vùng cuộn, ghim cột, tay kéo bề rộng, thanh chờ áp dụng, thanh kéo chiều cao ------
   (trước đây core tự bơm khối này vào <head>; nay nằm trong file để gói chỉ có 2 file nạp) */
.izy-scroll { position: relative; overflow: auto; overscroll-behavior: contain; width: 100%; max-width: 100%; min-width: 0; }
/* Bảng cao hơn khung nhìn: lăn tới đỉnh/đáy bảng phải chuyền sang trang, không thì phần trên bị kẹt ngoài màn (watchTall()). */
.izy-scroll.izy-scroll-tall { overscroll-behavior-y: auto; }
.izy-scroll > table { min-width: 100%; }
/* Trang có bảng izy: GIỮ CHỖ cố định cho thanh cuộn dọc của trang. Windows dùng thanh cuộn chiếm chỗ
   (~17px): trang cao xấp xỉ khung nhìn thì thanh cuộn bật/tắt -> trang hẹp/rộng 17px -> cột co giãn,
   tiêu đề đổi cao -> core đo lại -> thanh cuộn lại tắt/bật... bảng giật qua lại mỗi khung hình
   (2026-10-15, màn Danh mục địa điểm trên laptop khách). Giữ chỗ thì bề rộng trang không đổi nữa.
   macOS/thanh cuộn nổi: không ảnh hưởng gì. */
html:has(.izy-scroll) { scrollbar-gutter: stable; }
/* border-collapse:collapse + position:sticky dễ làm mất viền ở ô ghim. Chỉ đổi mô hình border,
   không ép màu nền/viền của ô: bảng vẫn giữ skin theme / CSS riêng của từng màn. */
.izy-sticky { border-collapse: separate !important; border-spacing: 0; }
.izy-sticky > thead th { position: relative; }
/* `border-collapse: separate` làm Bootstrap không còn tự hoà màu viền -> th kế thừa currentColor. */
.izy-sticky > thead > tr > th { border-color: var(--izy-grid-frame, var(--vz-border-color, #e9ebec)) !important; }
.izy-sticky > thead tr { border: 0 !important; margin: 0 !important; }
/* KẺ NGANG: `border-collapse: separate` (bắt buộc cho sticky) KHÔNG vẽ border đặt trên <tr>, mà
   Bootstrap lại đặt viền ngang của `.table` / `.table-bordered` ở <tr> — nên bảng izy chỉ còn kẻ
   dọc, mất sạch kẻ ngang, kể cả kẻ dưới hàng tiêu đề. Vẽ lại trên chính ô. Các chế độ viền riêng
   (izy-border-*) và .table-borderless đặc hiệu cao hơn nên vẫn tự quyết. */
.izy-sticky:not(.table-borderless):not(.izy-border-none):not(.izy-border-column) > thead > tr > *,
.izy-sticky:not(.table-borderless):not(.izy-border-none):not(.izy-border-column) > tbody > tr > *,
.izy-sticky:not(.table-borderless):not(.izy-border-none):not(.izy-border-column) > tfoot > tr > * {
    border-bottom: 1px solid var(--vz-table-border-color, var(--izy-tbl-border, #dfe2e7));
}
.izy-sticky:not(.table-borderless):not(.izy-border-none):not(.izy-border-column) > thead > tr:first-child > * {
    border-top: 1px solid var(--izy-grid-frame, #ced1d6);
}

/* Kiểu viền do init({border}) điều khiển. `inherit` không gắn class nên giữ nguyên skin của trang. */
.izy-sticky.izy-border-cell,
.izy-sticky.izy-border-column,
.izy-sticky.izy-border-none { border: 0 !important; }
.izy-sticky.izy-border-cell > thead > tr > *,
.izy-sticky.izy-border-cell > tbody > tr > *,
.izy-sticky.izy-border-cell > tfoot > tr > * {
    border: 0 !important;
    border-right: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
    border-bottom: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
}
.izy-sticky.izy-border-cell > thead > tr > *:first-child,
.izy-sticky.izy-border-cell > tbody > tr > *:first-child,
.izy-sticky.izy-border-cell > tfoot > tr > *:first-child {
    border-left: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
}
.izy-sticky.izy-border-cell > thead > tr:first-child > * {
    border-top: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
}
.izy-sticky.izy-border-column > thead > tr > *,
.izy-sticky.izy-border-column > tbody > tr > *,
.izy-sticky.izy-border-column > tfoot > tr > * {
    border: 0 !important;
    border-right: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
}
.izy-sticky.izy-border-column > thead > tr > *:first-child,
.izy-sticky.izy-border-column > tbody > tr > *:first-child,
.izy-sticky.izy-border-column > tfoot > tr > *:first-child {
    border-left: 1px solid var(--izy-grid-border, var(--vz-border-color, #e2e7ee)) !important;
}
.izy-sticky.izy-border-none > thead > tr > *,
.izy-sticky.izy-border-none > tbody > tr > *,
.izy-sticky.izy-border-none > tfoot > tr > * { border: 0 !important; }

.izy-pin { border: 0; background: transparent; padding: 0 2px; cursor: pointer; vertical-align: middle; color: var(--vz-secondary-color, #878a99); }
.izy-pin .bx { font-size: 14px; }
.izy-pin.active { color: var(--vz-primary, #405189); }
.izy-grip { position: absolute; top: 0; right: 0; width: 8px; height: 100%; cursor: col-resize; z-index: 5; }
.izy-grip::after { content: ""; position: absolute; top: 12%; right: 3px; width: 2px; height: 76%; border-radius: 1px; background: transparent; }
.izy-grip.izy-grip-left { left: 0; right: auto; }
.izy-grip.izy-grip-left::after { left: 3px; right: auto; }
.izy-grip:hover::after, .izy-grip.is-on::after { background: var(--vz-primary, #405189); }
.izy-overlay { position: fixed; inset: 0; z-index: 2147483646; cursor: col-resize; user-select: none; }
/* Vạch chỉ báo để `fixed` chứ không `absolute` trong vùng cuộn: con absolute của vùng cuộn định vị
   theo GỐC nội dung, đã cuộn xuống là vạch nằm ngoài tầm nhìn. */
.izy-guide { position: fixed; left: 0; width: 2px; z-index: 2147483647; pointer-events: none; background: var(--vz-primary, #405189); }
.izy-guide-label { position: absolute; top: 2px; left: 5px; padding: 1px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; line-height: 16px; white-space: nowrap; background: var(--vz-primary, #405189); color: #fff; }
/* Cột có thay đổi chờ áp dụng: gạch chân + số px mới. */
.izy-sticky > thead th.izy-dirty { box-shadow: inset 0 -3px 0 var(--vz-warning, #f7b84b); }
.izy-sticky > thead th.izy-dirty::before { content: attr(data-izy-w); position: absolute; top: 0; right: 12px; font-size: 10px; font-weight: 700; line-height: 12px; color: var(--vz-warning, #f7b84b); }
/* Thanh chờ áp dụng / "n dòng chờ lưu": cố định trên màn hình, gắn vào <body>. Dùng chung cho
   core (bề rộng cột) và edit (dòng sửa dở). */
.izy-pending { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 1090; display: flex; align-items: center; gap: 8px; padding: 9px 12px; border: 1px solid var(--vz-border-color, #e9ebec); border-radius: 6px; box-shadow: 0 6px 18px rgba(0,0,0,.18); background: var(--vz-secondary-bg, #fff); color: var(--vz-body-color, #212529); font-size: 13px; }
.izy-pending button { border: 1px solid var(--vz-border-color, #ced4da); border-radius: 4px; padding: 4px 10px; cursor: pointer; background: transparent; color: inherit; font-size: 12px; }
.izy-pending .izy-pending-apply { border-color: var(--vz-primary, #405189); background: var(--vz-primary, #405189); color: #fff; }
/* Thanh kéo chiều cao dưới bảng. */
.izy-resizer { height: 14px; cursor: ns-resize; display: flex; align-items: center; justify-content: center; position: relative; z-index: 5; touch-action: none; user-select: none; }
.izy-resizer span { display: block; width: 64px; height: 4px; border-radius: 2px; background: var(--vz-border-color, #ced4da); }
.izy-resizer:hover span { background: var(--vz-primary, #405189); }
body.izy-resizing { cursor: ns-resize !important; user-select: none !important; }
/* Hàng nhóm cột (izy-span-row data-izy-band-row): ô gộp thật, nhãn căn giữa cả nhóm. Nhãn dính
   trong lòng ô (offset trái/phải = bề rộng vùng ghim, core ghi) để nhóm chui nửa dưới cột ghim vẫn
   thấy chữ. */
thead > tr.izy-span-row > th.izy-band { text-align: center; white-space: nowrap; font-weight: 600; }
thead > tr.izy-span-row > th.izy-band > .izy-band-label { position: sticky; display: inline-block; }

/* ===== 20-edit.css ===== */
/* ---- Sửa tại chỗ (IzyTable.edit) — áp cho bảng có class .izy-editable (lib tự gắn khi bật) ------
 *
 * Hộp tĩnh và editor có cùng chiều cao để bảng không nhảy layout. Kích thước/màu lấy theo token
 * Velzon (--vz-font-base, --vz-input-*, --vz-border-*) thay vì hardcode px: font tính bằng rem nên
 * tự ăn theo dark mode và nút chỉnh cỡ chữ, hình học đúng cỡ .form-control-sm để vừa bảng nhiều cột.
 * Grid cột cho dropdown dạng bảng do từng trang tự khai theo `dropdownClass` riêng.
 */
table.izy-editable td.izy-edit {
    padding: 2px 3px !important;
    vertical-align: middle;
}

/* Một hệ kích thước chung cho hộp tĩnh + input + tom-select. */
table.izy-editable td.izy-edit .izy-box,
table.izy-editable td.izy-edit input.izy-inp,
table.izy-editable td.izy-edit textarea.izy-inp,
table.izy-editable td.izy-edit .ts-wrapper .ts-control {
    font-size: var(--vz-font-base, 0.8125rem);
    line-height: 1.5;
    min-height: calc(1.5em + 0.5rem + var(--vz-border-width, 1px) * 2);
    padding: 0.25rem 0.5rem;
    border-radius: var(--vz-border-radius-sm, 0.25rem);
}

table.izy-editable td.izy-edit .izy-box {
    display: flex;
    align-items: center;
    border: var(--vz-border-width, 1px) solid var(--vz-input-border-custom, var(--vz-border-color, #ced4da));
    background: var(--vz-input-bg-custom, var(--vz-secondary-bg, #fff));
    color: var(--vz-body-color);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

table.izy-editable td.izy-edit .izy-box.is-empty {
    color: var(--vz-danger, #f06548);
}

/* 1.6.1 — dòng phụ dưới ô (td[data-sub] → .izy-sub): chữ nhỏ, xám; trang tự đổi màu / thêm biểu tượng.
   Không giới hạn trong .izy-editable: ô không sửa được (thiếu quyền) vẫn hiện dòng phụ y hệt. */
.izy-sub {
    display: block;
    margin-top: .15rem;
    padding: 0 .1rem;
    font-size: .75rem;
    line-height: 1.25;
    color: var(--vz-secondary-color, #878a99);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

table.izy-editable tr:not(.izy-row-editable) > td.izy-edit .izy-box {
    border-color: transparent;
    background: transparent;
}

/*
 * 2026-08-21 — DẤU HIỆU "Ô NÀY BẤM SỬA ĐƯỢC".
 *
 * Khung viền không đủ: người dùng ít rành máy tính đọc khung viền là "ô hiển thị", không phải "ô
 * bấm được", nên họ mở trang chi tiết -> bấm Sửa -> lưu, mỗi lần đổi đúng một ô. Rê chuột vào giờ
 * đổi cả nền lẫn viền và hiện một cây bút — đủ để phát hiện ra, không đủ để làm bảng thành rừng
 * biểu tượng (bút chỉ hiện lúc hover, không chiếm chỗ lúc bình thường).
 */
/* :where() để selector này có độ ưu tiên 0 phần id: core sinh
   `position:sticky` cho cột ghim bằng selector (0,3,3); bản có `#id` là (1,2,2) nên THẮNG và
   biến cột ghim thành relative -> cột trôi theo khi cuộn ngang. Ô không ghim vẫn nhận relative
   vì mặc định là static, không có rule nào tranh. */
:where(table.izy-editable) tr.izy-row-editable > td.izy-edit { position: relative; }

table.izy-editable tr.izy-row-editable > td.izy-edit .izy-box { cursor: text; }
table.izy-editable tr.izy-row-editable > td.izy-edit .izy-box:hover {
    border-color: var(--vz-primary, #405189);
    background: var(--vz-primary-bg-subtle, #eaedf4);
}

table.izy-editable tr.izy-row-editable > td.izy-edit::after {
    content: "\f626";                   /* bx-pencil — bộ boxicons theme nạp sẵn ở master.blade.php */
    font-family: "boxicons", sans-serif;
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    font-size: 0.875rem;
    color: var(--vz-primary, #405189);
    opacity: 0;
    transition: opacity 0.12s ease-in-out;
    pointer-events: none;
}

table.izy-editable tr.izy-row-editable > td.izy-edit:hover::after { opacity: 0.75; }

/* Ô đang mở editor thì bút biến mất — chỗ đó là của con trỏ nhập liệu. */
table.izy-editable tr.izy-row-editable > td.izy-edit:has(input.izy-inp)::after,
table.izy-editable tr.izy-row-editable > td.izy-edit:has(textarea.izy-inp)::after,
table.izy-editable tr.izy-row-editable > td.izy-edit:has(.ts-wrapper)::after { opacity: 0; }

table.izy-editable td.izy-edit.is-dirty .izy-box {
    border-color: var(--vz-warning, #f7b84b);
    box-shadow: inset 0 0 0 1px var(--vz-warning, #f7b84b);
}

table.izy-editable td.izy-edit input.izy-inp {
    width: 100%;
    border: var(--vz-border-width, 1px) solid var(--vz-primary, #405189);
    outline: 0;
    background: var(--vz-input-bg-custom, var(--vz-secondary-bg, #fff));
    color: var(--vz-body-color, inherit);
}

/* 1.9.0 — ô giờ: ô gõ "HH:mm" + bảng chọn 2 cột (Giờ 00–23 · Phút 00–59) treo ở <body>, bám ô qua IzyTable.popup. */
table.izy-editable td.izy-edit input.izy-inp.izy-time { min-width: 4.5rem; font-variant-numeric: tabular-nums; }
.izy-timepick {
    position: absolute; z-index: 1090; width: 8.5rem;
    background: var(--vz-secondary-bg, #fff); color: var(--vz-body-color, #212529);
    border: var(--vz-border-width, 1px) solid var(--vz-border-color, #e9ebec);
    border-radius: var(--vz-border-radius, .25rem);
    box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
    font-size: .8125rem; font-variant-numeric: tabular-nums; user-select: none;
}
.izy-timepick-head, .izy-timepick-body { display: flex; }
.izy-timepick-head > span {
    flex: 1 1 0; text-align: center; padding: .25rem 0; font-weight: 600; font-size: .75rem;
    color: var(--vz-secondary-color, #878a99); border-bottom: 1px solid var(--vz-border-color, #e9ebec);
}
.izy-timepick-col { flex: 1 1 0; max-height: 13rem; overflow-y: auto; overscroll-behavior: contain; }
.izy-timepick-col + .izy-timepick-col { border-left: 1px solid var(--vz-border-color, #e9ebec); }
.izy-timepick-opt { padding: .2rem 0; text-align: center; cursor: pointer; }
.izy-timepick-opt:hover { background: var(--vz-tertiary-bg, #f3f6f9); }
.izy-timepick-opt.is-active { background: var(--vz-primary, #405189); color: #fff; }

table.izy-editable td.izy-edit textarea.izy-inp {
    display: block;
    width: 100%;
    min-width: 100%;
    max-width: 100%;
    overflow-y: hidden;
    resize: none;
    border: var(--vz-border-width, 1px) solid var(--vz-primary, #405189);
    outline: 0;
    background: var(--vz-input-bg-custom, var(--vz-secondary-bg, #fff));
    color: var(--vz-body-color, inherit);
}

table.izy-editable td.izy-edit .ts-wrapper {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: calc(1.5em + 0.5rem + var(--vz-border-width, 1px) * 2);
    margin: 0;
}
table.izy-editable td.izy-edit .ts-wrapper .ts-control {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    border-color: var(--vz-primary, #405189);
    flex-wrap: nowrap;
    overflow: hidden;
}
table.izy-editable td.izy-edit .ts-wrapper .ts-control > .item {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
table.izy-editable td.izy-edit .ts-wrapper .ts-control > input {
    min-width: 0 !important;
    margin: 0;
    line-height: inherit;
}
/* 1.5.3 — Skin tom-select.bootstrap5 chừa 2.25rem (36px) đệm phải cho mũi tên 16px, cộng 4px lề mỗi bên ô gõ
   (input_autogrow đã co ô gõ về 0px nhưng lề vẫn còn). Ô 117px chỉ còn ~65px cho chữ ⇒ biển số 15LD-01810 (76px)
   thành "15LD-01…" ngay khi bấm sửa, dù ô tĩnh .izy-box cùng bề rộng hiện đủ. Trong ô bảng: mũi tên nhỏ sát mép
   phải, bỏ lề ô gõ ⇒ trả lại ~24px cho chữ. */
table.izy-editable td.izy-edit .ts-wrapper.single { --ts-pr-caret: 1.25rem; }
table.izy-editable td.izy-edit .ts-wrapper.single .ts-control {
    background-position: right .3rem center;
    background-size: 12px 9px;
}
table.izy-editable td.izy-edit .ts-wrapper.has-items .ts-control > input { margin: 0 !important; }

/* Ô KHOÁ theo hàng (data-locked-fields): vẽ như ô chỉ đọc, không bút, không đổi màu khi rê. */
table.izy-editable tr.izy-row-editable > td.izy-edit.is-locked .izy-box { border-color: transparent; background: transparent; cursor: default; }
table.izy-editable tr.izy-row-editable > td.izy-edit.is-locked .izy-box:hover { border-color: transparent; background: transparent; }
table.izy-editable tr.izy-row-editable > td.izy-edit.is-locked::after { display: none; }
/* Dòng server từ chối (batch/1 dòng): row-danger + lý do ở title; ô vẫn bẩn (viền vàng) để sửa tiếp. */

/* ===== 25-select.css ===== */
/* ---- Tom Select client/server của IzyTable.select ------------------------------------------ */
.izy-select-dropdown.ts-dropdown {
    margin: 0;
    z-index: 1065;
    border: 1px solid var(--vz-border-color, #ced4da);
    background: var(--vz-card-bg, var(--vz-secondary-bg, #fff));
    color: var(--vz-body-color, #212529);
    box-shadow: 0 5px 12px rgba(0, 0, 0, .18);
}
.izy-select-dropdown .ts-dropdown-content { max-height: 320px; }
.izy-select-dropdown .option,
.izy-select-dropdown .no-results,
.izy-select-dropdown .loading,
.izy-select-dropdown .loading-more {
    padding: .3rem .5rem;
    font-size: .8125rem;
}
.izy-select-dropdown .option.active {
    background: var(--vz-primary, #405189);
    color: #fff;
}
.izy-select-dropdown .no-results,
.izy-select-dropdown .loading,
.izy-select-dropdown .loading-more {
    color: var(--vz-secondary-color, #878a99);
    font-style: italic;
}
/* Dropdown dạng BẢNG (option `columns`) — 1.5.1: kẻ ô mảnh 1px giữa các cột và giữa các dòng cho dễ dò
   theo cột (anh Cảnh 2026-09-24). Đệm nằm trên từng ô thay vì trên .option để đường kẻ chạm mép. */
.izy-select-row { display: flex; gap: 0; align-items: stretch; }
.izy-select-row--unmatched .izy-select-cell { flex: 1 1 100%; width: 100% !important; border-right: 0; }
/* 1.6.1 — option giữ chỗ của lookupCurrent (chờ server trả dòng thật): ẩn hẳn. `hidden` không đủ vì
   .izy-select-row đặt display:flex. */
.izy-select-dropdown .izy-select-row--pending,
.izy-select-dropdown .option:has(> .izy-select-row--pending) { display: none !important; }
/* 1.6.1 — ô TÌM của dropdown (plugin dropdown_input): đứng trên cùng, có kính lúp, viền + focus theo token.
   CSS gốc tom-select.bootstrap5 chỉ kẻ một gạch chân xám, không focus, không biểu tượng — nhìn như dòng trống.
   Kính lúp là SVG nhúng để gói không phụ thuộc bộ icon của app. */
.izy-select-dropdown .dropdown-input-wrap {
    padding: .35rem .5rem;
    border-bottom: 1px solid var(--vz-border-color, #e9ebec);
    background: var(--vz-card-bg, var(--vz-secondary-bg, #fff));
}
.izy-select-dropdown .dropdown-input-wrap .dropdown-input {
    display: block;
    width: 100%;
    padding: .25rem .5rem .25rem 1.75rem;
    border: 1px solid var(--vz-input-border-custom, var(--vz-border-color, #ced4da));
    border-radius: var(--vz-border-radius-sm, .2rem);
    background-color: var(--vz-input-bg-custom, var(--vz-body-bg, #fff));
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23878a99' stroke-width='2.5' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: .55rem center;
    background-size: .8rem;
    color: var(--vz-body-color, inherit);
    font-size: .8125rem;
    line-height: 1.5;
    box-shadow: none;
}
.izy-select-dropdown .dropdown-input-wrap .dropdown-input:focus {
    outline: 0;
    border-color: rgba(var(--vz-primary-rgb, 64, 81, 137), .65);
    box-shadow: 0 0 0 .15rem rgba(var(--vz-primary-rgb, 64, 81, 137), .12);
}
.izy-select-dropdown .dropdown-input-wrap .dropdown-input::placeholder {
    color: var(--vz-secondary-color, #878a99);
    opacity: .8;
}
/* Dòng đang chọn của ô (lookupCurrent ghim lên đầu): nền nhạt khi con trỏ đã rời sang dòng khác. */
.izy-select-dropdown .option.izy-current-option:not(.active) {
    background: rgba(var(--vz-primary-rgb, 64, 81, 137), .1);
    font-weight: 600;
}
.izy-select-cell {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    padding: .3rem .5rem;
    border-right: 1px solid var(--vz-border-color, #e9ebec);
}
.izy-select-cell:last-child { border-right: 0; }
.izy-select-dropdown .option:has(> .izy-select-row) {
    padding: 0;
    border-bottom: 1px solid var(--vz-border-color, #e9ebec);
}
.izy-select-dropdown .option.active:has(> .izy-select-row) .izy-select-cell { border-right-color: rgba(255, 255, 255, .35); }
.izy-select-head {
    padding: 0;
    border-bottom: 1px solid var(--vz-border-color, #e9ebec);
    background: var(--vz-tertiary-bg, #f3f6f9);
    color: var(--vz-emphasis-color, var(--vz-body-color, #212529));
    font-size: .75rem;
    font-weight: 600;
    text-transform: uppercase;
}
.izy-select-dropdown--multi .option.selected {
    background: rgba(var(--vz-primary-rgb, 64, 81, 137), .1);
    color: var(--vz-body-color, #212529);
}
.izy-hf-multi-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .4rem .5rem;
    border-top: 1px solid var(--vz-border-color, #e9ebec);
    background: var(--vz-tertiary-bg, #f3f6f9);
}
.izy-hf-multi-selected {
    color: var(--vz-secondary-color, #878a99);
    font-size: .75rem;
    white-space: nowrap;
}
.izy-select-dropdown--md,
.izy-select-dropdown--lg,
.izy-select-dropdown--xl {
    width: max-content !important;
    max-width: calc(100vw - 16px);
}
.izy-select-dropdown--md { min-width: 320px; }
.izy-select-dropdown--lg { min-width: 480px; }
.izy-select-dropdown--xl { min-width: 760px; }
@media (max-width: 820px) {
    .izy-select-dropdown--lg, .izy-select-dropdown--xl { min-width: 92vw; }
}

/* ===== 30-filter.css ===== */
/* ---- Hàng lọc dưới tiêu đề cột (IzyTable.filter) — <tr class="izy-filter-row"> + blade/_filter_cell ----
   Phần dropdown do src/css/25-select.css lo. */

/* ---- Hàng lọc trong thead ---------------------------------------------------------------
   Widget phải co theo cột: width 100% + min-width 0 để không kéo giãn bảng auto-layout. */
.izy-filter-row th {
    padding: .125rem .1875rem !important;
    margin: 0 !important;
    border-top: 1px solid var(--vz-border-color, #e9ebec) !important;
}
.izy-filter-row select,
.izy-filter-row input { width: 100%; min-width: 0; }
.izy-filter-row input {
    min-height: calc(1.5em + .5rem + 2px); height: auto; padding: .25rem .5rem; font-size: .75rem;
    border: var(--vz-border-width, 1px) solid var(--vz-input-border-custom, var(--vz-border-color, #ced4da));
    border-radius: var(--vz-border-radius-sm, .2rem);
    background: var(--vz-input-bg-custom, var(--vz-body-bg, #fff)); color: var(--vz-body-color, inherit);
}
.izy-filter-row input:focus {
    outline: 0; border-color: rgba(var(--vz-primary-rgb, 64, 81, 137), .65);
    box-shadow: 0 0 0 .15rem rgba(var(--vz-primary-rgb, 64, 81, 137), .12);
}
.izy-filter-row .ts-wrapper { min-width: 0; font-size: .75rem; }
.izy-filter-row .ts-control {
    min-height: calc(1.5em + .5rem + 2px); padding: .25rem 1.75rem .25rem .5rem;
    font-size: .75rem; border-color: var(--vz-input-border-custom, var(--vz-border-color, #ced4da));
    border-radius: var(--vz-border-radius-sm, .2rem); background: var(--vz-input-bg-custom, var(--vz-body-bg, #fff));
}

/* ---- Multi-select LUÔN 1 DÒNG: 1 mục chỉ hiện tag; từ 2 mục hiện badge `(tổng)` đỏ ở đầu
   + tối đa 2 tag. Ô lọc nằm trong thead nên cao thêm một dòng là lệch toàn bộ toạ độ cột ghim,
   vì thế khoá cứng 28px thay vì để Tom Select tự xuống dòng. `data-count` do JS gắn. ---------- */
.izy-filter-row .ts-wrapper.multi .ts-control {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    height: 28px !important;
    min-height: 28px !important;
    max-height: 28px !important;
    overflow: hidden !important;
    white-space: nowrap !important;
    text-overflow: ellipsis !important;
    padding: 2px 28px 2px 4px !important;
    gap: 3px !important;
}
.izy-filter-row .ts-wrapper.multi.has-multiple-items .ts-control {
    padding-left: 34px !important; /* chừa chỗ cho badge số đếm */
}
.izy-filter-row .ts-wrapper.multi .ts-control > .item {
    font-size: 11px !important;
    line-height: 16px !important;
    padding: 1px 5px !important;
    margin: 0 !important;
    background: rgba(var(--vz-primary-rgb, 64, 81, 137), 0.12) !important;
    color: var(--vz-primary, #405189) !important;
    border: 1px solid rgba(var(--vz-primary-rgb, 64, 81, 137), 0.25) !important;
    border-radius: 3px !important;
    white-space: nowrap !important;
    max-width: 85px !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    flex-shrink: 0 !important;
}
.izy-filter-row .ts-wrapper.multi.has-many-items .ts-control > .item:nth-child(n+3) {
    display: none !important;
}
.izy-filter-row .ts-wrapper.multi.has-multiple-items::before {
    content: "(" attr(data-count) ")";
    position: absolute;
    left: 5px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    font-size: 10px;
    font-weight: 700;
    padding: 1px 5px;
    background: var(--vz-danger, #f06548);
    color: #fff;
    border-radius: 999px;
    pointer-events: none;
    line-height: 16px;
}

/* Lớp phủ mờ trong lúc pjax fetch lại tbody. */
.izy-hf-loading {
    position: absolute; z-index: 30; display: flex;
    align-items: center; justify-content: center; background: rgba(128, 128, 128, .12);
}

/* ---- Numrange: ô 1 dòng mở popover mini (Từ ≥ / Đến ≤ + phím tắt) ----------------------- */
.izy-hf-numrange-wrap {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}
.izy-hf-numrange-input {
    width: 100%;
    cursor: pointer;
    text-overflow: ellipsis;
    padding-right: 1.5rem !important;
}
.izy-hf-numrange-input.is-filtered {
    border-color: var(--vz-primary, #405189) !important;
    background-color: rgba(var(--vz-primary-rgb, 64, 81, 137), 0.08) !important;
    color: var(--vz-primary, #405189) !important;
    font-weight: 500;
}
.izy-hf-numrange-icon {
    position: absolute;
    right: .45rem;
    color: var(--vz-secondary-color, #878a99);
    font-size: .85rem;
    pointer-events: none;
}
.izy-hf-numrange-clear {
    position: absolute;
    right: .3rem;
    width: 18px;
    height: 18px;
    border: none;
    background: transparent;
    color: var(--vz-secondary-color, #878a99);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
}
.izy-hf-numrange-clear:hover {
    color: var(--vz-danger, #f06548);
    background-color: rgba(var(--vz-danger-rgb, 240, 101, 72), 0.12);
}

/* ---- Daterange: có nút xoá ngay trên ô, không phải mở lịch mới thấy Cancel --------------- */
.izy-hf-daterange-wrap {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}
.izy-hf-daterange-input { padding-right: 2.75rem !important; }
.izy-hf-daterange-input.is-filtered {
    border-color: var(--vz-primary, #405189) !important;
    background-color: rgba(var(--vz-primary-rgb, 64, 81, 137), 0.08) !important;
    color: var(--vz-primary, #405189) !important;
    font-weight: 500;
}
.izy-hf-daterange-clear {
    position: absolute;
    /* Chừa vùng cột Actions sticky phủ lên mép phải khi bảng đang cuộn ngang. */
    right: 1.5rem;
    width: 18px;
    height: 18px;
    border: 0;
    border-radius: 50%;
    padding: 0;
    background: transparent;
    color: var(--vz-secondary-color, #878a99);
    font-size: 1.1rem;
    line-height: 18px;
    cursor: pointer;
    z-index: 2;
}
.izy-hf-daterange-clear:hover {
    color: var(--vz-danger, #f06548);
    background-color: rgba(var(--vz-danger-rgb, 240, 101, 72), 0.12);
}

/* ---- Popover của numrange (light + dark) ------------------------------------------------ */
.izy-hf-numrange-popover {
    width: 290px;
    background-color: var(--vz-card-bg-custom, var(--vz-card-bg, var(--vz-secondary-bg, #fff)));
    color: var(--vz-body-color, #212529);
    border: 1px solid var(--vz-border-color, #e9ebec);
    border-radius: var(--vz-border-radius, .375rem);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
    padding: .85rem;
    animation: izyHfFadeIn 0.15s ease-out;
}
[data-bs-theme="dark"] .izy-hf-numrange-popover,
[data-layout-mode="dark"] .izy-hf-numrange-popover {
    background-color: #212529 !important;
    background: #212529 !important;
    color: #ced4da !important;
    border-color: #32384a !important;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.6) !important;
}
@keyframes izyHfFadeIn {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: translateY(0); }
}
.izy-hf-popover-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: .6rem;
    padding-bottom: .4rem;
    border-bottom: 1px solid var(--vz-border-color, #e9ebec);
}
[data-bs-theme="dark"] .izy-hf-popover-header,
[data-layout-mode="dark"] .izy-hf-popover-header {
    border-bottom-color: #32384a !important;
}
.izy-hf-popover-title {
    font-size: .8125rem;
    font-weight: 600;
    color: var(--vz-heading-color, var(--vz-body-color));
}
.izy-hf-popover-close-btn {
    border: none;
    background: transparent;
    font-size: 1.25rem;
    line-height: 1;
    color: var(--vz-secondary-color, #878a99);
    cursor: pointer;
}
.izy-hf-popover-close-btn:hover { color: var(--vz-body-color); }
.izy-hf-popover-sublabel {
    font-size: .7rem;
    font-weight: 500;
    text-transform: uppercase;
    color: var(--vz-secondary-color, #878a99);
    margin-bottom: .25rem;
    display: block;
}
.izy-hf-presets {
    margin-top: .6rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .3rem;
}
.izy-hf-preset-title {
    font-size: .7rem;
    color: var(--vz-secondary-color, #878a99);
    margin-right: .2rem;
}
.izy-hf-preset-chip {
    border: 1px solid var(--vz-border-color, #ced4da);
    background: var(--vz-tertiary-bg, #f3f6f9);
    color: var(--vz-body-color);
    border-radius: var(--vz-border-radius-sm, .2rem);
    font-size: .6875rem;
    padding: .15rem .45rem;
    cursor: pointer;
    transition: all .15s ease;
}
.izy-hf-preset-chip:hover {
    border-color: var(--vz-primary, #405189);
    background-color: rgba(var(--vz-primary-rgb, 64, 81, 137), 0.1);
    color: var(--vz-primary, #405189);
}
[data-bs-theme="dark"] .izy-hf-preset-chip,
[data-layout-mode="dark"] .izy-hf-preset-chip {
    background: #2a3042 !important;
    border-color: #32384a !important;
    color: #ced4da !important;
}
[data-bs-theme="dark"] .izy-hf-preset-chip:hover,
[data-layout-mode="dark"] .izy-hf-preset-chip:hover {
    background: rgba(var(--vz-primary-rgb, 64, 81, 137), 0.25) !important;
    border-color: var(--vz-primary, #405189) !important;
    color: #fff !important;
}
[data-bs-theme="dark"] .izy-hf-popover-footer,
[data-layout-mode="dark"] .izy-hf-popover-footer {
    border-top-color: #32384a !important;
}

/* ===== 35-tools.css ===== */
/* ---- Công tắc công cụ cột (IzyTable.tools, src/45-tools.js) ------------------------------------
   Một class trên <html> ẩn cùng lúc hàng lọc dưới tiêu đề, dòng tổng ở chân bảng và thanh phụ của trang
   ([data-izy-tools], vd. "Gom theo"). !important + tiền tố html: thanh phụ hay mang .d-flex của Bootstrap
   (display:flex !important). */
html.izy-tools-off .izy-filter-row,
html.izy-tools-off tr.izy-agg-row,
html.izy-tools-off [data-izy-tools] { display: none !important; }
/* Trang không có công cụ cột nào: gói đặt `hidden` lên nút — .btn của theme không được thắng. */
[data-izy-tools-toggle][hidden] { display: none !important; }

/* ===== 40-popup.css ===== */
/* ---- Popup bám ô (IzyTable.popup): trạng thái "ô neo đã bị cuộn khuất" ------------------------
   Giấu popup nhưng GIỮ nguyên trạng thái mở + chữ đang gõ; cuộn ô trở lại là thấy ngay.
   Phải !important: Tom Select ghi thẳng `style.visibility = 'visible'` lên phần tử mỗi lần mở, còn
   flatpickr bật hiện bằng rule `.flatpickr-calendar.open { visibility: visible }`. */
.izy-popup-away {
    visibility: hidden !important;
    pointer-events: none !important;
}

/* ===== 50-user.css ===== */
/*
 * izy-user.css — Ô hiển thị NHÂN VIÊN dùng chung (ảnh đại diện + tên ngắn, hover ra "mã - họ tên").
 * Markup do HUser::chip() nhả ra (và IzyTable.edit vẽ lại y hệt sau khi sửa ô):
 *
 *   <span class="izy-user" title="JNR128 - Nguyễn Thị Thu Trang">
 *       <img class="izy-user-ava" src="...">            hoặc  <span class="izy-user-ava izy-user-ava-tx">TT</span>
 *       <span class="izy-user-name">Ms Trang</span>
 *   </span>
 *
 * Đường kính ảnh đọc từ biến --izy-user-ava (mặc định 22px) nên một ô to hơn (trang chi tiết) chỉ
 * cần đặt lại biến, không phải viết class mới. Màu lấy token Velzon để ăn theo dark mode.
 */
.izy-user {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
    max-width: 100%;
    vertical-align: middle;
}

.izy-user-ava {
    flex: 0 0 auto;
    width: var(--izy-user-ava, 22px);
    height: var(--izy-user-ava, 22px);
    border-radius: 50%;
    overflow: hidden;
    object-fit: cover;
    background: var(--vz-primary-bg-subtle, #eff2f7);
}

/* Không có ảnh -> vòng tròn chữ cái đầu của TÊN GỌI (không phải chữ cái của mã NV). */
.izy-user-ava-tx {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--vz-primary, #405189);
    font-weight: 500;
    font-size: calc(var(--izy-user-ava, 22px) * 0.42);
    line-height: 1;
    text-transform: uppercase;
}

.izy-user-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Không có người phụ trách / người tạo. */
.izy-user-empty {
    color: var(--vz-secondary-color, #878a99);
}

/* ===== 60-skin.css ===== */
/* ---- Skin bảng chuẩn (rút từ crm-customers.css của CRM, bỏ scope [data-crm-object]) ---------- */
.izy-scroll > table td,
.izy-scroll > table th { font-size: calc(12px + var(--f-inc, 0px)); }
.izy-scroll > table th { white-space: normal; overflow-wrap: break-word; min-width: 56px; }
/* Ô tên/nội dung dài: chặn bề ngang, cắt "…", giữ title để rê chuột đọc. */
.izy-cell-clip { min-width: 0; max-width: 320px; }
.izy-cell-clip > a, .izy-cell-clip > span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Link sắp xếp trong <th> (blade/_th_sort): không gạch chân, cột đang sort in đậm. */
/* Nhãn sort PHẢI xuống dòng được: `nowrap` cũ làm mọi tiêu đề có link sắp xếp bị cắt cụt
   (overflow:hidden của widthCss) ngay khi kéo cột hẹp lại, thay vì gãy dòng như tiêu đề thường.
   Kéo thật hẹp thì icon mũi tên có thể rơi xuống dòng riêng — chấp nhận, đổi lại nhãn không cụt. */
.izy-sort { color: inherit; text-decoration: none; white-space: normal; overflow-wrap: break-word; }
.izy-sort .bx { font-size: .85em; opacity: .55; vertical-align: -1px; }
.izy-sort.is-on { font-weight: 600; }
.izy-sort.is-on .bx { opacity: 1; color: var(--vz-primary, #405189); }

/* ===== 70-group.css ===== */
/* ---- Dòng tổng + gom nhóm (IzyTable.aggregate) ------------------------------------------------ */
.izy-agg-row th { background: var(--izy-st-foot-bg, var(--vz-tertiary-bg, #f3f6f9)); }
/* Ô colspan trải HẾT bề rộng bảng nên position:sticky trên chính nó không trượt được. Ghim NHÃN
   bên trong: nhãn trượt trong lòng ô, kéo ngang vẫn bám mép trái vùng cuộn. */
.izy-group-head td { background: var(--izy-grid-head-bg, var(--vz-tertiary-bg, #f3f6f9)); }
.izy-group-head .izy-group-label { position: sticky; left: .5rem; display: inline-block; }
.izy-group-toggle { cursor: pointer; }

/* ===== 80-setting.css ===== */
/* ---- Offcanvas "Cài đặt hiển thị" (blade/card_setting) --------------------------------------
   Panel trượt từ phải: bảng đứng yên chỗ cũ, vừa chỉnh vừa nhìn. Bên trong panel hẹp nên mọi
   .row của Bootstrap được ép xuống một cột — trang không phải khai lại lưới riêng cho panel. */
.izy-setting-panel { width: 460px; max-width: 100%; }
.izy-setting-panel .offcanvas-body { display: flex; flex-direction: column; gap: 1rem; }
.izy-setting-panel .row > [class*="col-"] { flex: 0 0 auto; width: 100%; }
.izy-setting-panel .offcanvas-footer {
    border-top: 1px solid var(--vz-border-color, #e9ebec);
    background: var(--vz-secondary-bg, #fff);
    padding: .75rem 1rem;
}
.izy-setting-block + .izy-setting-block { border-top: 1px solid var(--vz-border-color, #e9ebec); padding-top: 1rem; }
.izy-setting-title { font-size: 13px; font-weight: 600; margin-bottom: .25rem; }

/* Danh sách cột: một dòng = tay kéo + tích hiện/ẩn + tên. Cuộn trong panel, không đẩy footer. */
.izy-col-list { max-height: 46vh; overflow-y: auto; margin-bottom: 0; }
.izy-col-item { cursor: default; padding: .35rem .6rem; }
.izy-col-item .izy-col-grip { cursor: grab; color: var(--vz-secondary-color, #878a99); line-height: 1; }
.izy-col-item .izy-col-grip:active { cursor: grabbing; }
.izy-col-item .izy-col-label { cursor: pointer; font-size: 13px; overflow-wrap: anywhere; }
.izy-col-item .izy-col-check { flex: 0 0 auto; cursor: pointer; }
.izy-col-item.izy-col-off .izy-col-label { color: var(--vz-secondary-color, #878a99); }
.izy-col-item.sortable-ghost { opacity: .45; }
.izy-col-item.sortable-chosen { background: var(--vz-tertiary-bg, #f3f6f9); }
.izy-col-head { font-size: 12px; color: var(--vz-secondary-color, #878a99); }
