Update besar apk finance

This commit is contained in:
Wian Drs
2026-09-11 16:03:00 +07:00
parent d42483b350
commit 4ef15c22ae
897 changed files with 98056 additions and 15979 deletions
+901
View File
@@ -0,0 +1,901 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
:root {
--brand: #ee7800;
--brand-dark: #c85d00;
--brand-soft: #fff2e5;
--ink: #20242c;
--muted: #717784;
--line: #e7e9ee;
--canvas: #f5f6f8;
--surface: #fff;
--sidebar: #17191e;
--sidebar-soft: #24272e;
--radius: 12px;
--shadow: 0 5px 18px rgba(25, 29, 38, 0.07);
--sidebar-width: 258px;
--topbar-height: 72px;
--tap: 44px;
}
html {
background: var(--canvas);
}
body.app-v2 {
margin: 0;
background: var(--canvas);
color: var(--ink);
font-family: Inter, "Segoe UI", sans-serif;
font-size: 12px;
overflow-x: hidden;
}
.app-v2 * {
box-sizing: border-box;
}
.app-shell {
min-height: 100vh;
}
.app-sidebar {
position: fixed;
inset: 0 auto 0 0;
width: var(--sidebar-width);
z-index: 1030;
background: var(--sidebar);
color: #fff;
flex-direction: column;
border-right: 1px solid #ffffff0d;
}
.sidebar-brand,
.drawer-brand {
height: 76px;
display: flex;
align-items: center;
gap: 12px;
padding: 0 18px;
color: #fff;
text-decoration: none;
}
.brand-mark {
width: 40px;
height: 40px;
border-radius: 11px;
background: linear-gradient(145deg, var(--brand), #ff9d3f);
display: grid;
place-items: center;
box-shadow: 0 6px 16px #ee78004d;
flex: none;
}
.brand-mark img {
width: 27px;
height: 27px;
object-fit: contain;
}
.sidebar-brand strong,
.drawer-brand strong {
display: block;
font-size: 15px;
letter-spacing: 0.1px;
}
.sidebar-brand small,
.drawer-brand small {
display: block;
color: #a7abb3;
font-size: 10px;
letter-spacing: 1.1px;
text-transform: uppercase;
margin-top: 2px;
}
.sidebar-search {
height: 42px;
margin: 5px 14px 12px;
display: flex;
align-items: center;
gap: 9px;
padding: 0 12px;
background: #ffffff0a;
border: 1px solid #ffffff12;
border-radius: 10px;
color: #8d929c;
}
.sidebar-search input {
width: 100%;
height: 38px;
min-height: 0 !important;
border: 0;
outline: 0;
background: transparent;
color: #eee;
font-size: 12px;
}
.sidebar-search input::placeholder {
color: #777c86;
}
.sidebar-nav {
padding: 0 10px 16px;
overflow-y: auto;
overflow-x: hidden;
flex: 1;
scrollbar-width: thin;
scrollbar-color: #3a3e47 transparent;
}
.app-menu-item {
margin: 2px 0;
}
.nav-group {
border: 0;
}
.nav-group > summary {
list-style: none;
cursor: pointer;
}
.nav-group > summary::-webkit-details-marker {
display: none;
}
.nav-entry {
width: 100%;
min-height: 43px;
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
border-radius: 9px;
color: #b9bdc5 !important;
text-decoration: none !important;
font-weight: 500;
font-size: 12.5px;
transition:
background 0.16s,
color 0.16s;
}
.nav-entry:hover {
background: #ffffff0b;
color: #fff !important;
transform: none !important;
box-shadow: none !important;
}
.nav-entry.active,
.nav-group.is-active > summary {
background: linear-gradient(90deg, #ee780025, #ee78000a);
color: #fff !important;
}
.nav-entry.active {
box-shadow: inset 3px 0 var(--brand);
}
.nav-entry-icon {
width: 27px;
height: 27px;
border-radius: 7px;
display: grid;
place-items: center;
color: #9297a1;
font-size: 14px;
flex: none;
}
.nav-entry.active .nav-entry-icon,
.nav-group.is-active > summary .nav-entry-icon {
color: #ff9d3f;
}
.nav-entry-label {
flex: 1;
}
.nav-badge {
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 10px;
background: #d9363e;
color: #fff;
display: grid;
place-items: center;
font-size: 10px;
}
.nav-chevron {
font-size: 10px;
color: #70757e;
transition: transform 0.18s;
}
.nav-group[open] > summary .nav-chevron {
transform: rotate(180deg);
}
.nav-children {
margin: 2px 0 6px 23px;
padding: 2px 0 2px 18px;
border-left: 1px solid #343841;
}
.nav-child {
min-height: 34px;
display: flex;
align-items: center;
gap: 9px;
padding: 5px 9px;
color: #9499a3 !important;
text-decoration: none !important;
border-radius: 7px;
font-size: 11.5px;
}
.nav-child > span {
width: 4px;
height: 4px;
background: #555a64;
border-radius: 50%;
flex: none;
}
.nav-child:hover,
.nav-child.active {
background: #ffffff08;
color: #fff !important;
}
.nav-child.active > span {
background: var(--brand);
box-shadow: 0 0 0 3px #ee780025;
}
.sidebar-user {
margin: 10px 12px 14px;
padding: 10px;
display: flex;
align-items: center;
gap: 9px;
background: var(--sidebar-soft);
border: 1px solid #ffffff0a;
border-radius: 11px;
}
.user-avatar {
width: 34px;
height: 34px;
display: grid;
place-items: center;
border-radius: 9px;
background: var(--brand);
font-weight: 700;
}
.user-copy {
min-width: 0;
flex: 1;
}
.user-copy strong,
.user-copy small {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-copy strong {
font-size: 11.5px;
}
.user-copy small {
font-size: 10px;
color: #8d929b;
}
.sidebar-user > a {
width: 30px;
height: 30px;
display: grid;
place-items: center;
color: #8d929b;
border-radius: 7px;
}
.sidebar-user > a:hover {
color: #fff;
background: #ffffff0c;
}
.app-workspace {
min-height: 100vh;
margin-left: var(--sidebar-width);
}
.app-topbar {
height: var(--topbar-height);
position: sticky;
top: 0;
z-index: 1020;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
background: #fffffff2;
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.topbar-leading,
.topbar-actions {
display: flex;
align-items: center;
gap: 10px;
}
.topbar-kicker {
display: block;
color: var(--muted);
font-size: 9px;
letter-spacing: 1.1px;
text-transform: uppercase;
margin-bottom: 2px;
}
.app-topbar h1 {
font-size: 18px;
line-height: 1.2;
margin: 0;
font-weight: 700;
}
.icon-button {
width: 40px;
height: 40px;
min-height: 40px !important;
display: grid;
place-items: center;
border: 1px solid var(--line);
border-radius: 10px;
background: #fff;
color: #535965;
text-decoration: none;
font-size: 16px;
}
.icon-button:hover {
background: var(--brand-soft);
border-color: #ffd2a8;
color: var(--brand-dark);
}
.quick-button {
background: var(--brand);
border-color: var(--brand);
color: #fff;
}
.quick-button:hover {
background: var(--brand-dark);
color: #fff;
}
.app-content {
min-height: calc(100vh - var(--topbar-height));
padding: 0 24px 30px;
}
.app-breadcrumb {
padding: 13px 1px;
background: transparent !important;
border: 0 !important;
position: static !important;
}
.app-breadcrumb .breadcrumb {
font-size: 11px;
color: var(--muted);
}
.app-breadcrumb a {
color: var(--brand-dark);
text-decoration: none;
}
.app-v2 .container,
.app-v2 .container-fluid {
max-width: none;
}
.app-content > .container,
.app-content > .container-fluid {
padding-left: 0 !important;
padding-right: 0 !important;
margin-top: 0 !important;
}
.app-v2 .card,
.app-v2 .modern-card,
.app-v2 .table-card {
border: 1px solid var(--line) !important;
border-radius: var(--radius) !important;
background: var(--surface);
box-shadow: var(--shadow) !important;
overflow: hidden;
}
.app-v2 .card-header {
padding: 13px 16px;
background: #fff !important;
border-bottom: 1px solid var(--line) !important;
color: var(--ink) !important;
font-weight: 600;
}
.app-v2 .card-body {
padding: 16px;
}
.app-v2 .summary-card {
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
background: #fff;
}
.app-v2 .summary-card:hover {
transform: none;
border-color: #ffc58c;
}
.app-v2 .table-responsive,
.app-v2 .table-responsive-premium {
border: 1px solid var(--line);
border-radius: 10px;
background: #fff;
overflow: auto;
}
.app-v2 table,
.app-v2 .table,
.app-v2 .modern-table {
margin: 0;
border-collapse: collapse !important;
border-spacing: 0 !important;
}
.app-v2 .table > :not(caption) > * > *,
.app-v2 table.modern-table td {
padding: 11px 12px;
border-bottom: 1px solid #eceef2 !important;
vertical-align: middle;
}
.app-v2 .table thead th,
.app-v2 table.modern-table thead th {
padding: 10px 12px;
background: #f7f8fa !important;
color: #5b616d !important;
border: 0 !important;
border-bottom: 1px solid var(--line) !important;
border-radius: 0 !important;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.55px;
white-space: nowrap;
}
.app-v2 .table tbody tr,
.app-v2 table.modern-table tbody tr {
background: #fff !important;
box-shadow: none !important;
transform: none !important;
}
.app-v2 .table tbody tr:hover,
.app-v2 table.modern-table tbody tr:hover {
background: #fffaf5 !important;
transform: none !important;
}
.app-v2 .form-label {
margin-bottom: 6px;
font-size: 11.5px;
font-weight: 600;
color: #4a505b;
}
.app-v2 .form-control,
.app-v2 .form-select {
min-height: 42px;
border: 1px solid #dfe2e7;
border-radius: 9px;
background-color: #fff;
font-size: 13px;
}
.app-v2 .form-control:focus,
.app-v2 .form-select:focus {
border-color: #f4a154;
box-shadow: 0 0 0 3px #ee780018;
}
.app-v2 .btn {
min-height: 38px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
box-shadow: none !important;
transform: none !important;
}
.app-v2 .btn-primary,
.app-v2 .btn-warning,
.app-v2 .btn-add,
.app-v2 .btn-eximport {
background: var(--brand) !important;
border-color: var(--brand) !important;
color: #fff !important;
}
.app-v2 .btn-primary:hover,
.app-v2 .btn-warning:hover,
.app-v2 .btn-add:hover,
.app-v2 .btn-eximport:hover {
background: var(--brand-dark) !important;
}
.app-v2 .modal-content {
border: 0;
border-radius: 14px;
box-shadow: 0 24px 70px #15171c40;
}
.app-v2 .modal-header {
padding: 15px 18px;
border-color: var(--line);
}
.app-v2 .modal-body {
padding: 18px;
}
.app-v2 .modal-footer {
border-color: var(--line);
}
.command-search {
position: relative;
}
.command-search i {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: #888;
z-index: 2;
}
.command-search input {
padding-left: 40px;
}
.quick-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.quick-action {
min-height: 88px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 11px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 7px;
text-decoration: none;
color: var(--ink);
background: #fff;
}
.quick-action:hover {
border-color: #ffc58c;
background: var(--brand-soft);
color: var(--brand-dark);
}
.quick-action i {
font-size: 20px;
color: var(--brand);
}
.search-menu-results a {
border-color: var(--line) !important;
color: var(--ink);
border-radius: 7px;
}
.search-menu-results a:hover {
background: var(--brand-soft);
}
.global-loading {
position: fixed;
inset: 0;
z-index: 2050;
display: grid;
place-items: center;
background: #17191e35;
backdrop-filter: blur(2px);
}
.global-loading[hidden] {
display: none;
}
.loading-card {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
background: #fff;
border-radius: 12px;
box-shadow: 0 15px 45px #1114;
font-weight: 600;
}
.loading-card .spinner-border {
width: 20px;
height: 20px;
color: var(--brand);
}
.dataTables_wrapper {
padding: 4px;
}
.dataTables_wrapper .row {
margin: 0 0 10px;
align-items: center;
}
.dataTables_wrapper .dataTables_filter input {
min-height: 36px;
border: 1px solid var(--line);
border-radius: 8px;
margin-left: 7px;
}
.dataTables_wrapper .dataTables_length select {
min-height: 34px;
border: 1px solid var(--line);
border-radius: 7px;
}
.page-item.active .page-link {
background: var(--brand);
border-color: var(--brand);
}
.page-link {
color: var(--brand-dark);
}
.select2-container .select2-selection--single {
height: 42px !important;
border-color: #dfe2e7 !important;
border-radius: 9px !important;
}
.select2-container .select2-selection--single .select2-selection__rendered {
line-height: 40px !important;
}
.select2-container .select2-selection--single .select2-selection__arrow {
height: 40px !important;
}
.ajax-error-toast {
position: fixed;
right: 18px;
bottom: 18px;
z-index: 3000;
max-width: 420px;
padding: 12px 15px;
background: #9f2630;
color: #fff;
border-radius: 10px;
box-shadow: 0 16px 35px #1114;
}
.mobile-action-bar {
display: none !important;
}
@media (max-width: 991.98px) {
:root {
--topbar-height: 64px;
}
.app-workspace {
margin-left: 0;
}
.app-topbar {
height: 64px;
padding: 0 14px;
}
.topbar-kicker {
display: none;
}
.app-topbar h1 {
font-size: 16px;
}
.app-content {
padding: 0 14px 82px;
}
.app-mobile-drawer {
width: min(88vw, 340px) !important;
background: var(--sidebar) !important;
color: #fff;
border: 0 !important;
border-radius: 0 !important;
padding: 0 !important;
}
.app-mobile-drawer .offcanvas-header {
height: 72px;
padding: 0 15px;
border-bottom: 1px solid #ffffff10;
}
.app-mobile-drawer .drawer-brand {
height: auto;
padding: 0;
}
.app-mobile-drawer .btn-close {
filter: invert(1);
opacity: 0.8;
}
.app-mobile-drawer .offcanvas-body {
padding: 8px 6px 20px;
}
.mobile-action-bar {
position: fixed;
left: 10px;
right: 10px;
bottom: 9px;
height: 60px;
z-index: 1040;
display: grid !important;
grid-template-columns: repeat(4, 1fr);
padding: 5px;
background: #fff;
border: 1px solid var(--line);
border-radius: 15px;
box-shadow: 0 10px 35px #20242c25;
}
.mobile-action-bar a,
.mobile-action-bar button {
min-height: 48px;
border: 0;
border-radius: 10px;
background: transparent;
color: #696f79;
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1px;
font-size: 9px;
}
.mobile-action-bar i {
font-size: 17px;
color: var(--brand);
}
.mobile-action-bar a:hover,
.mobile-action-bar button:hover {
background: var(--brand-soft);
}
.app-v2 .modal-dialog {
margin: 8px;
}
.app-v2 .modal-content {
border-radius: 13px;
}
.ajax-error-toast {
left: 12px;
right: 12px;
bottom: 78px;
max-width: none;
}
}
@media (max-width: 767.98px) {
body.app-v2 {
font-size: 13px;
}
.app-content {
padding-left: 10px;
padding-right: 10px;
}
.app-breadcrumb {
padding: 10px 1px;
}
.app-breadcrumb .breadcrumb {
flex-wrap: nowrap;
overflow: auto;
white-space: nowrap;
}
.app-v2 .card-body {
padding: 12px;
}
.app-v2 .card-header {
padding: 11px 12px;
}
.app-v2 .row {
--bs-gutter-x: 12px;
}
.app-v2 .table-responsive {
border: 0;
border-radius: 0;
}
.app-v2 .responsive-detail-table:not(.dataTable) thead {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
.app-v2 .responsive-detail-table:not(.dataTable) tbody,
.app-v2 .responsive-detail-table:not(.dataTable) tr,
.app-v2 .responsive-detail-table:not(.dataTable) td {
display: block;
width: 100%;
}
.app-v2 .responsive-detail-table:not(.dataTable) tr {
margin: 8px 0;
border: 1px solid var(--line);
border-radius: 10px;
overflow: hidden;
}
.app-v2 .responsive-detail-table:not(.dataTable) td {
display: flex !important;
justify-content: space-between;
gap: 12px;
text-align: right !important;
}
.app-v2 .responsive-detail-table:not(.dataTable) td:before {
content: attr(data-label);
font-weight: 600;
text-align: left;
color: #5b616d;
}
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_filter input {
width: 100%;
margin: 3px 0;
}
.quick-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 992px) {
.app-v2 .modal-dialog {
max-width: min(720px, calc(100vw - 40px));
}
.app-v2 .modal-dialog.modal-lg {
max-width: min(980px, calc(100vw - 50px));
}
.app-v2 .modal-dialog.modal-xl {
max-width: min(1200px, calc(100vw - 50px));
}
}
@media print {
.app-sidebar,
.app-topbar,
.app-breadcrumb,
.mobile-action-bar,
.btn,
form {
display: none !important;
}
.app-workspace {
margin: 0;
}
.app-content {
padding: 0;
}
.app-v2 .card {
border: 0 !important;
box-shadow: none !important;
}
}
/* Standar global modal: dialog selalu berada di tengah viewport. */
.app-v2 .modal-dialog.modal-dialog-centered {
min-height: calc(100% - 2rem);
}
.app-v2 .modal-dialog.modal-dialog-centered .modal-content {
width: 100%;
}
.app-v2 .approval-focus {
outline: 3px solid rgba(238, 120, 0, 0.42);
outline-offset: -2px;
background: #fff7ee !important;
animation: approval-focus-pulse 1.2s ease-in-out 2;
}
@keyframes approval-focus-pulse {
50% {
box-shadow: 0 0 0 7px rgba(238, 120, 0, 0.12);
}
}
.nav-badge.nav-badge-warning {
background: #f2b705;
color: #302400;
font-weight: 700;
}
.nav-badge.nav-badge-danger {
background: #d9363e;
color: #fff;
font-weight: 700;
}
/* Alias komponen lama yang masih dipakai view, dengan visual template baru. */
.app-v2 .btn-action {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
margin: 2px;
padding: 6px 10px;
border: 1px solid transparent;
border-radius: 8px;
text-decoration: none;
}
.app-v2 .btn-edit {
background: #fff7e8;
color: #9a5b00;
border-color: #f2c66d;
}
.app-v2 .btn-edit:hover {
background: #ffedc8;
color: #704100;
}
.app-v2 .btn-delete {
background: #fff0f1;
color: #b4232f;
border-color: #efadb2;
}
.app-v2 .btn-delete:hover {
background: #ffe0e3;
color: #8d1721;
}
.app-v2 .btn-detail {
background: #edf6ff;
color: #1769b0;
border-color: #a9d2f5;
}
.app-v2 .btn-detail:hover {
background: #dceeff;
color: #105487;
}
.app-v2 .btn-add,
.app-v2 .btn-eximport {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 7px 13px;
border-radius: 8px;
text-decoration: none;
}
+1
View File
@@ -0,0 +1 @@
/* Deprecated compatibility asset. Responsive application rules now live in app-final.css. */
+38
View File
@@ -0,0 +1,38 @@
.asset-page{--asset-blue:#1769e0;--asset-ink:#172033;--asset-muted:#687386;color:var(--asset-ink)}.asset-hero{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:18px}.asset-hero h2{font-size:1.45rem;margin:2px 0 4px}.asset-hero p,.section-heading p{color:var(--asset-muted);margin:0}.eyebrow{font-size:.68rem;letter-spacing:.13em;color:#798397;font-weight:700}.asset-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.asset-metrics article{background:#fff;border:1px solid #e5e9f0;border-radius:14px;padding:16px;box-shadow:0 4px 16px rgba(22,32,51,.04)}.asset-metrics span,.asset-metrics small{display:block;color:var(--asset-muted);font-size:.78rem}.asset-metrics strong{display:block;font-size:1.18rem;margin:5px 0}.asset-tabs{display:flex;gap:7px;padding:6px;background:#fff;border:1px solid #e4e9f1;border-radius:14px;margin-bottom:14px;overflow:auto}.asset-tabs button{border:0;background:transparent;padding:10px 15px;border-radius:9px;color:#526077;white-space:nowrap}.asset-tabs button.active{background:var(--asset-blue);color:#fff;box-shadow:0 5px 13px rgba(23,105,224,.22)}.asset-tabs i{margin-right:6px}.asset-card{border:1px solid #e4e9f1;border-radius:14px;box-shadow:0 5px 18px rgba(25,36,60,.04);overflow:hidden}.asset-card .card-header{background:#fff;display:flex;align-items:center;justify-content:space-between;padding:14px 16px}.filter-grid{display:grid;grid-template-columns:2fr repeat(3,1fr);gap:10px}.section-heading{display:flex;align-items:center;justify-content:space-between;margin:18px 2px 12px}.section-heading h3{font-size:1.1rem;margin:0 0 3px}.status-pill{display:inline-flex;border-radius:20px;padding:4px 8px;font-size:.68rem;font-weight:700;background:#edf1f6;color:#526077}.status-submitted,.status-approved{background:#fff1c7;color:#8b6000}.status-capitalized,.status-posted,.status-active{background:#dff7ea;color:#087344}.status-rejected,.status-lost,.status-damaged,.status-disposed{background:#ffe2e4;color:#aa2530}.source-choice{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}.source-choice input{position:absolute;opacity:0}.source-choice span{display:flex;flex-direction:column;border:1px solid #dfe5ee;border-radius:12px;padding:15px;cursor:pointer}.source-choice input:checked+span{border-color:var(--asset-blue);background:#f2f7ff;box-shadow:0 0 0 2px rgba(23,105,224,.1)}.source-choice i{font-size:1.25rem;color:var(--asset-blue)}.source-choice small,.form-hint{color:var(--asset-muted)}.form-section{border:1px solid #e6eaf0;border-radius:12px;padding:15px;margin-bottom:14px}.form-section h6{font-weight:700;margin-bottom:12px}.source-info{border-radius:10px;background:#f4f7fb;padding:11px;color:#536075}.file-grid{display:flex;flex-wrap:wrap;gap:10px}.file-add,.file-tile{width:92px;height:92px;border:1px dashed #cbd3df;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-direction:column;position:relative;overflow:visible;background:#fff}.file-add{cursor:pointer;color:#536075}.file-tile img{width:100%;height:100%;object-fit:cover;border-radius:9px}.file-tile .pdf-icon{font-size:2rem;color:#dc3545}.file-tile button{position:absolute;right:-7px;top:-7px;width:25px;height:25px;border-radius:50%;border:2px solid #fff;background:#e43844;color:#fff;z-index:2;line-height:18px}.report-controls{display:grid;grid-template-columns:2fr 1fr 1fr repeat(3,auto);gap:9px}.report-result{margin-top:15px;overflow:auto}.empty-state{text-align:center;color:#778197;padding:38px!important}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.detail-panel{border:1px solid #e3e8f0;border-radius:12px;padding:14px}.detail-panel h6{font-weight:700}.detail-kv{display:grid;grid-template-columns:150px 1fr;gap:6px 12px;font-size:.86rem}.detail-kv span:nth-child(odd){color:#718096}.timeline{border-left:2px solid #dde5ef;margin-left:7px;padding-left:16px}.timeline article{margin-bottom:13px}.timeline small{color:#718096}.action-grid{display:flex;gap:7px;flex-wrap:wrap}.money{text-align:right;font-variant-numeric:tabular-nums}#pdfModal iframe{width:100%;height:min(80vh,850px);border:0}.opname-line-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr 2fr;gap:8px;align-items:center;padding:9px 0;border-bottom:1px solid #edf0f4}
@media(max-width:991.98px){.asset-metrics{grid-template-columns:1fr 1fr}.filter-grid,.report-controls{grid-template-columns:1fr 1fr}.detail-grid{grid-template-columns:1fr}.modal-dialog-scrollable{height:100%;margin:0;max-width:none}.modal-dialog-scrollable .modal-content{min-height:100%;border-radius:0}.opname-line-grid{grid-template-columns:1fr 1fr}.opname-line-grid>*:first-child{grid-column:1/-1}}
@media(max-width:575.98px){.asset-hero{align-items:flex-start;flex-direction:column}.asset-metrics,.filter-grid,.report-controls,.source-choice{grid-template-columns:1fr}.asset-tabs{position:sticky;top:0;z-index:3}.asset-tabs button span{font-size:.78rem}.asset-page{padding-bottom:72px}.detail-kv{grid-template-columns:1fr}.detail-kv span:nth-child(odd){margin-top:6px}}
#assetFormModal .modal-dialog{height:calc(100vh - 2rem);max-height:calc(100vh - 2rem)}#assetFormModal .modal-content,#assetFormModal form{height:100%;min-height:0}#assetFormModal form{display:flex;flex-direction:column}#assetFormModal .modal-header,#assetFormModal .modal-footer{flex:0 0 auto}#assetFormModal .modal-body{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
@media(max-width:991.98px){#assetFormModal .modal-dialog{height:100%;max-height:100%;margin:0}#assetFormModal .modal-content{height:100vh;max-height:100vh}#assetFormModal .modal-body{padding-bottom:90px}}
/* Dialog transaksi yang dibuka dari Detail Aset dan tombol Buat Opname. */
body[data-page="fixed_assets"] .swal2-container{z-index:1095;padding:18px}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast){width:min(94vw,620px);max-height:calc(100vh - 36px);padding:0;border:1px solid #e2e8f0;border-radius:18px;overflow:hidden;box-shadow:0 24px 70px rgba(20,31,51,.22);font-size:.92rem}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-icon{width:3.7rem;height:3.7rem;margin:22px auto 8px;font-size:.85rem}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-title{padding:20px 24px 4px;color:#172033;font-size:1.2rem;font-weight:700;line-height:1.35}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-icon+.swal2-title{padding-top:8px}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container{display:block;max-height:calc(100vh - 230px);margin:0;padding:14px 24px 18px;overflow-x:hidden;overflow-y:auto;color:#4c596d;text-align:left;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container>input,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container>select,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container>textarea,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-input,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-select,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-textarea,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-file{display:block;width:100%;height:auto;min-height:44px;margin:0 0 12px;padding:10px 12px;border:1px solid #d7dee9;border-radius:10px;background:#fff;color:#172033;font:inherit;box-shadow:none;transition:border-color .15s,box-shadow .15s}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-textarea{min-height:92px;resize:vertical}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-file{padding:8px;background:#f8fafc;font-size:.84rem}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-input:focus,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-select:focus,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-textarea:focus{border-color:#1769e0;outline:0;box-shadow:0 0 0 3px rgba(23,105,224,.12)}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-input-label,
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container label{display:block;margin:2px 0 6px!important;color:#344158;font-size:.78rem;font-weight:700;text-align:left}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .asset-swal-label{letter-spacing:.01em}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .select2-container{display:block;width:100%!important;margin-bottom:12px;text-align:left}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .select2-selection{min-height:44px;border-color:#d7dee9;border-radius:10px;padding:7px 9px}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-validation-message{margin:0 24px 10px;border-radius:9px;background:#fff0f1;color:#a51f2b;font-size:.8rem}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-actions{width:100%;margin:0;padding:14px 24px;border-top:1px solid #e7ebf1;background:#f8fafc;justify-content:flex-end;gap:8px}
body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-actions button{min-width:112px;margin:0;padding:10px 16px;border:0;border-radius:9px;font-size:.86rem;font-weight:700;box-shadow:none}
body[data-page="fixed_assets"] .swal2-confirm{background:#1769e0!important}
body[data-page="fixed_assets"] .swal2-cancel{background:#eef1f5!important;color:#3e4a5f!important}
#assetDetailActions{gap:8px;flex-wrap:wrap;justify-content:flex-end}
@media(max-width:575.98px){body[data-page="fixed_assets"] .swal2-container{padding:0;align-items:flex-end}body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast){width:100%;max-height:94vh;border-radius:18px 18px 0 0}body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-title{padding-left:18px;padding-right:18px}body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-html-container{max-height:calc(94vh - 190px);padding-left:18px;padding-right:18px}body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-actions{display:grid;grid-template-columns:1fr 1fr;padding:12px 18px calc(12px + env(safe-area-inset-bottom))}body[data-page="fixed_assets"] .swal2-popup:not(.swal2-toast) .swal2-actions button{width:100%;min-width:0}#assetDetailActions{justify-content:stretch}#assetDetailActions .btn{flex:1 1 calc(50% - 8px)}}
+11
View File
@@ -0,0 +1,11 @@
.inventory-hero{background:linear-gradient(125deg,#17253f,#263b62 62%,#ee7800);border-radius:20px;padding:22px 24px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:20px;box-shadow:0 14px 32px rgba(23,37,63,.16)}
.inventory-hero h2{font-weight:750;font-size:1.3rem}.inventory-hero p{color:rgba(255,255,255,.78)}.inventory-kicker{font-size:.68rem;font-weight:800;letter-spacing:.16em;color:#ffd8ad}.inventory-scan-link{white-space:nowrap}
.inventory-module-nav{display:flex;gap:8px;overflow:auto;padding:7px;background:#fff;border:1px solid #e4e9f1;border-radius:15px;box-shadow:0 6px 18px rgba(27,39,60,.05);scrollbar-width:thin}.inventory-module-nav a{display:flex;align-items:center;gap:7px;padding:10px 13px;border-radius:10px;color:#536075;text-decoration:none;font-size:.82rem;font-weight:650;white-space:nowrap}.inventory-module-nav a:hover{background:#f5f7fb;color:#1e65e8}.inventory-module-nav a.active{background:#1f6ff2;color:#fff;box-shadow:0 5px 12px rgba(31,111,242,.22)}
.inventory-metrics .metric-card,.inventory-card{background:#fff;border:1px solid #e5eaf1;border-radius:16px;box-shadow:0 7px 22px rgba(32,45,68,.055)}.metric-card{padding:16px;height:100%;display:flex;flex-direction:column}.metric-icon{font-size:1.25rem;margin-bottom:13px}.metric-card strong{font-size:1.12rem;color:#1b2433}.metric-card small{color:#748096;margin-top:3px}
.inventory-card{overflow:hidden}.inventory-card-head{padding:16px 18px;border-bottom:1px solid #edf0f5;display:flex;align-items:center;justify-content:space-between;gap:12px}.inventory-card-head h5{font-size:.96rem;font-weight:750;margin:0;color:#202a3a}.inventory-card-head p{font-size:.78rem;color:#7a8597;margin:3px 0 0}.inventory-list a{display:flex;align-items:center;gap:12px;padding:13px 17px;border-bottom:1px solid #eef1f5;color:#273246;text-decoration:none}.inventory-list a:last-child{border:0}.inventory-list a:hover{background:#f8faff}.inventory-list strong,.inventory-list small{display:block}.inventory-list small{font-size:.75rem;color:#7b8799;margin-top:2px}.item-avatar{width:38px;height:38px;border-radius:11px;background:#eaf2ff;color:#1f6ff2;display:grid;place-items:center}.inventory-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;padding:38px 20px;color:#8490a2}.inventory-empty i{font-size:1.5rem;color:#54af84}.inventory-loading{padding:22px;text-align:center;color:#657187}
.status-chip{display:inline-flex;padding:4px 8px;border-radius:20px;font-size:.66rem;font-weight:800;letter-spacing:.03em;background:#eef1f5;color:#596477}.status-draft{background:#eef1f5;color:#5e6878}.status-submitted,.status-reserved{background:#fff2cc;color:#936100}.status-approved,.status-active,.status-available{background:#dff4e9;color:#147447}.status-posted,.status-released,.status-consumed{background:#dceaff;color:#155cc1}.status-rejected,.status-reversed,.status-cancelled,.status-sold_out{background:#fde2e4;color:#ad2931}
.scan-product{border:1px solid #dfe6ef;border-radius:14px;padding:16px}.scan-product h5{font-size:.8rem;margin:12px 0}.scan-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}.scan-grid span{font-size:.7rem;color:#798497}.scan-grid strong{display:block;font-size:.8rem;color:#283346;margin-top:2px}.scan-history{margin:14px 0 0;padding:12px 0 0 18px;border-top:1px solid #edf0f4}.scan-history li{margin-bottom:7px;font-size:.76rem}.scan-history span{display:block;color:#7b8697}
.inventory-toolbar{display:flex;gap:10px;align-items:end;flex-wrap:wrap}.inventory-toolbar .form-label{font-size:.7rem;font-weight:700;color:#68758a}.inventory-toolbar .filter-field{min-width:150px}.inventory-table thead th{font-size:.67rem;text-transform:uppercase;letter-spacing:.06em;color:#69758a;background:#f7f9fc;white-space:nowrap}.inventory-table td{font-size:.8rem}.inventory-line-table select,.inventory-line-table input{min-width:115px}.inventory-line-table .line-item{min-width:240px}.inventory-balance-box{background:#f6f8fb;border:1px solid #e1e6ed;border-radius:12px;padding:12px}.inventory-balance-box div{display:flex;justify-content:space-between;font-size:.78rem;padding:3px 0}.document-meta{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.document-meta span{font-size:.7rem;color:#778399}.document-meta strong{display:block;color:#263247;font-size:.82rem;margin-top:2px}.timeline-list{list-style:none;padding:0;margin:0}.timeline-list li{border-left:2px solid #dce5f2;padding:0 0 14px 15px;margin-left:5px;font-size:.78rem}.timeline-list li strong,.timeline-list li span{display:block}.timeline-list li span{color:#7c8798}
.inventory-operation-tabs{display:grid;grid-template-columns:repeat(7,minmax(125px,1fr));gap:8px;padding:8px;background:#fff;border:1px solid #e3e9f1;border-radius:16px;overflow:auto}.inventory-operation-tabs button{border:0;border-radius:11px;background:#f5f7fb;color:#526078;padding:11px 10px;display:flex;align-items:center;justify-content:center;gap:7px;white-space:nowrap;font-size:.78rem;font-weight:700}.inventory-operation-tabs button:hover{background:#eaf1fc;color:#1f65d7}.inventory-operation-tabs button.active{background:#1f6ff2;color:#fff;box-shadow:0 5px 12px rgba(31,111,242,.2)}
.inventory-attachments{display:flex;gap:10px;flex-wrap:wrap;padding-top:4px}.attachment-add,.attachment-tile{width:92px;height:92px;border:1px dashed #cbd4e1;border-radius:12px;background:#fff;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#607086;overflow:visible}.attachment-add{cursor:pointer}.attachment-add:hover{border-color:#1f6ff2;color:#1f6ff2;background:#f7faff}.attachment-add i{font-size:1.35rem}.attachment-add span,.attachment-tile small{font-size:.66rem;margin-top:4px}.attachment-tile img{width:100%;height:100%;object-fit:cover;border-radius:11px}.attachment-tile small{position:absolute;left:4px;right:4px;bottom:3px;background:rgba(255,255,255,.88);padding:2px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attachment-file-icon{font-size:2rem;color:#d84b55}.remove-attachment{position:absolute;z-index:2;right:-8px;top:-8px;width:25px;height:25px;border-radius:50%;border:2px solid #fff;background:#dc3545;color:#fff;display:grid;place-items:center;padding:0;box-shadow:0 2px 7px rgba(0,0,0,.22)}
@media(max-width:767.98px){.inventory-hero{padding:18px;border-radius:15px;align-items:flex-start}.inventory-hero p{font-size:.78rem}.inventory-scan-link{padding:8px}.inventory-scan-link{font-size:0}.inventory-scan-link i{font-size:.8rem}.inventory-module-nav{margin-left:-4px;margin-right:-4px}.inventory-module-nav a{padding:9px 11px}.inventory-module-nav a span{display:none}.inventory-module-nav a.active span{display:inline}.inventory-operation-tabs{display:flex;margin-left:-4px;margin-right:-4px}.inventory-operation-tabs button{min-width:145px}.scan-grid,.document-meta{grid-template-columns:1fr}.inventory-card-head{align-items:flex-start}.modal.inventory-modal .modal-dialog{margin:0;max-width:none;height:100%}.modal.inventory-modal .modal-content{height:100%;border-radius:0}.modal.inventory-modal .modal-body{overflow:auto}.inventory-toolbar>*{width:100%}.inventory-toolbar .filter-field{min-width:100%}}
+206
View File
@@ -0,0 +1,206 @@
.invoice-editor-page .invoice-editor-grid,
.invoice-editor-page .invoice-editor-grid.is-creating {
grid-template-columns: minmax(310px, 360px) minmax(0, 1fr);
align-items: start;
}
.invoice-editor-main {
grid-column: 2;
grid-row: 1;
min-width: 0;
}
.invoice-editor-sidebar {
grid-column: 1;
grid-row: 1;
}
.invoice-editor-sidebar-inner {
position: sticky;
top: 82px;
display: grid;
gap: 12px;
}
.add-invoice-item-button {
min-height: 52px;
box-shadow: 0 8px 20px rgba(239, 125, 0, .18);
}
.invoice-sidebar-actions .card-body {
display: grid;
gap: 12px;
}
.sidebar-next-status small {
display: block;
margin-bottom: 3px;
color: #667085;
}
.sidebar-next-status b {
display: block;
line-height: 1.4;
}
.sidebar-action-buttons {
display: grid;
gap: 8px;
}
.sidebar-action-buttons .btn {
width: 100%;
}
.auto-rule-note,
.selected-customer-preview {
display: block;
margin-top: 12px;
padding: 10px 12px;
border-radius: 9px;
background: #f8fafc;
color: #667085;
font-size: 12px;
}
.invoice-item-modal .modal-content {
max-height: 92vh;
overflow: hidden;
}
.invoice-item-modal #invoiceLineForm {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.invoice-item-modal .modal-header,
.invoice-item-modal .modal-footer {
flex: 0 0 auto;
}
.invoice-item-modal .modal-body {
flex: 1 1 auto;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
}
.invoice-item-modal .modal-header small { display: block; color: #667085; }
.invoice-item-modal .modal-body { padding: 20px; }
.invoice-item-modal label { margin-bottom: 5px; font-size: 12px; font-weight: 600; }
.invoice-item-modal .barcode-panel { min-height: 150px; }
.invoice-item-modal .selected-barcodes {
max-height: 220px;
overflow-y: auto;
padding-right: 4px;
}
.invoice-item-modal .line-type-switch {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.invoice-batch-common {
padding: 16px;
border: 1px solid #dbe5f0;
border-radius: 12px;
background: #f8fbff;
}
.invoice-batch-composer {
margin-top: 16px;
padding: 16px;
border: 1px solid #e4e7ec;
border-radius: 12px;
background: #fff;
}
.invoice-batch-list {
overflow: hidden;
border: 1px solid #e4e7ec;
border-radius: 12px;
}
.invoice-batch-list > div:first-child {
padding: 12px 14px 0;
}
.invoice-batch-list small,
.invoice-batch-list td small {
display: block;
color: #667085;
}
.invoice-batch-list .batch-empty-row td {
padding: 26px 12px;
color: #667085;
text-align: center;
}
.batch-live-summary {
display: flex;
flex-wrap: wrap;
gap: 8px 18px;
}
.batch-live-summary span {
display: grid;
gap: 2px;
min-width: 125px;
color: #667085;
font-size: 11px;
}
.batch-live-summary b,
.batch-live-summary strong {
color: #1d2939;
font-size: 14px;
}
.invoice-item-footer {
justify-content: space-between;
gap: 16px;
}
.invoice-item-footer .line-live-total {
min-width: 240px;
margin: 0;
}
.invoice-item-footer > div:last-child { display: flex; gap: 8px; }
.invoice-item-modal.editing-line .invoice-batch-list,
.invoice-item-modal.editing-line .batch-live-summary,
.invoice-item-modal.editing-line .batch-submit-button {
display: none !important;
}
.invoice-item-modal.editing-line .invoice-item-footer {
justify-content: flex-end;
}
@media (max-width: 991px) {
.invoice-editor-page .invoice-editor-grid,
.invoice-editor-page .invoice-editor-grid.is-creating { grid-template-columns: 1fr; }
.invoice-editor-main,
.invoice-editor-sidebar { grid-column: auto; grid-row: auto; }
.invoice-editor-sidebar { order: -1; }
.invoice-editor-sidebar-inner { position: static; }
}
@media (max-width: 767px) {
.invoice-item-modal .modal-content {
min-height: 100%;
max-height: 100dvh;
}
.invoice-item-footer { align-items: stretch; flex-direction: column; }
.invoice-item-footer .line-live-total,
.invoice-item-footer > div:last-child,
.invoice-item-footer .btn { width: 100%; }
.batch-live-summary { width: 100%; }
.batch-live-summary span { flex: 1 1 30%; min-width: 90px; }
.invoice-item-modal .line-type-switch { grid-template-columns: 1fr; }
}
+557
View File
@@ -0,0 +1,557 @@
.invoice-workspace {
padding: 20px;
background: #f4f6f9;
min-height: calc(100vh - 72px);
color: #1d2939;
}
.invoice-hero {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 18px;
margin-bottom: 8px;
}
.invoice-hero > div:first-child > span {
font-size: 11px;
letter-spacing: 0.14em;
color: #667085;
}
.invoice-hero h2 {
margin: 3px 0 2px;
font-weight: 700;
}
.invoice-hero p,
.editor-card .card-header small,
.invoice-list-card .card-header small {
display: block;
color: #667085;
}
.hero-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.sales-section-nav {
display: flex;
gap: 7px;
padding: 7px;
margin-bottom: 16px;
background: #fff;
border: 1px solid #e4e7ec;
border-radius: 13px;
overflow-x: auto;
}
.sales-section-nav a {
display: flex;
align-items: center;
gap: 7px;
padding: 9px 13px;
border-radius: 8px;
color: #475467;
text-decoration: none;
white-space: nowrap;
}
.sales-section-nav a.active {
background: #ef7d00;
color: #fff;
}
.sales-section-nav b {
min-width: 19px;
padding: 1px 6px;
border-radius: 10px;
background: #dc3545;
color: #fff;
text-align: center;
font-size: 11px;
}
.invoice-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.invoice-metrics article {
padding: 15px;
background: #fff;
border: 1px solid #e4e7ec;
border-radius: 12px;
}
.invoice-metrics small,
.invoice-metrics span {
display: block;
color: #667085;
}
.invoice-metrics strong {
display: block;
margin: 5px 0;
font-size: 20px;
}
.invoice-list-card,
.editor-card {
border: 1px solid #e4e7ec;
border-radius: 13px;
box-shadow: 0 5px 20px rgba(16, 24, 40, 0.04);
overflow: hidden;
}
.invoice-list-card > .card-header,
.editor-card > .card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 14px 16px;
background: #fff;
}
.invoice-filters {
display: grid;
grid-template-columns: repeat(5, minmax(135px, 1fr)) auto;
gap: 10px;
align-items: end;
padding: 14px 16px;
background: #f8fafc;
border-top: 1px solid #eaecf0;
border-bottom: 1px solid #eaecf0;
}
.invoice-filters label,
.item-form-card label {
font-size: 12px;
font-weight: 600;
margin-bottom: 5px;
}
.filter-action {
display: flex;
gap: 6px;
}
.invoice-list-card th,
.item-list-card th {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #667085;
background: #f8fafc;
white-space: nowrap;
}
.invoice-list-card td small,
.item-list-card td small {
display: block;
color: #667085;
}
.invoice-table-actions {
display: flex;
gap: 5px;
flex-wrap: wrap;
min-width: 215px;
}
.invoice-status {
display: inline-flex;
padding: 4px 9px;
border-radius: 15px;
font-size: 11px;
font-weight: 700;
background: #eef2f6;
color: #344054;
}
.status-draft {
background: #e0f2fe;
color: #026aa2;
}
.status-submitted {
background: #fef0c7;
color: #b54708;
}
.status-posted {
background: #dcfae6;
color: #067647;
}
.status-rejected {
background: #fee4e2;
color: #b42318;
}
#invoicePdfModal iframe {
width: 100%;
height: min(84vh, 940px);
border: 0;
}
#invoicePdfModal .modal-content {
max-height: 94vh;
}
.invoice-progress {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
margin-bottom: 15px;
background: #eaecf0;
border-radius: 11px;
overflow: hidden;
}
.invoice-progress span {
padding: 11px;
text-align: center;
background: #f8fafc;
color: #667085;
font-size: 12px;
}
.invoice-progress span.done {
background: #ecfdf3;
color: #067647;
font-weight: 700;
}
.invoice-editor-grid {
display: grid;
grid-template-columns: minmax(330px, 37%) minmax(0, 63%);
gap: 15px;
align-items: start;
}
.invoice-editor-grid.is-creating {
grid-template-columns: minmax(360px, 42%) minmax(0, 58%);
}
.invoice-editor-left {
display: grid;
gap: 15px;
}
.editor-card .card-body {
padding: 16px;
}
.disabled-card .card-body,
.invoice-empty {
min-height: 180px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
color: #667085;
}
.disabled-card i,
.invoice-empty i {
font-size: 36px;
color: #98a2b3;
margin-bottom: 8px;
}
.invoice-rule-list {
display: grid;
gap: 0;
margin: 0;
}
.invoice-rule-list > div {
display: flex;
justify-content: space-between;
gap: 15px;
padding: 8px 0;
border-bottom: 1px solid #eaecf0;
}
.invoice-rule-list dt {
color: #667085;
font-weight: 500;
}
.invoice-rule-list dd {
text-align: right;
font-weight: 600;
margin: 0;
}
.invoice-note {
margin-top: 12px;
padding: 10px;
border-radius: 9px;
background: #f8fafc;
}
.invoice-note small {
display: block;
color: #667085;
}
.line-type-switch {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.line-type-switch input {
position: absolute;
opacity: 0;
}
.line-type-switch span {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
padding: 10px;
border: 1px solid #d0d5dd;
border-radius: 9px;
cursor: pointer;
font-size: 12px;
}
.line-type-switch input:checked + span {
border-color: #ef7d00;
background: #fff6ed;
color: #b54708;
font-weight: 700;
}
.form-text {
font-size: 11px;
color: #667085;
}
.barcode-panel {
padding: 12px;
border: 1px solid #e4e7ec;
border-radius: 10px;
background: #f8fafc;
}
.barcode-scan {
display: flex;
gap: 6px;
}
.selected-barcodes {
display: grid;
gap: 6px;
margin-top: 9px;
}
.selected-barcode {
display: grid;
grid-template-columns: minmax(0, 1fr) 90px 30px;
gap: 7px;
align-items: center;
padding: 7px;
background: #fff;
border: 1px solid #e4e7ec;
border-radius: 8px;
}
.selected-barcode small {
display: block;
color: #667085;
overflow: hidden;
text-overflow: ellipsis;
}
.selected-barcode button {
border: 0;
background: transparent;
color: #dc3545;
}
.line-live-total {
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px 0 10px;
padding: 11px;
border-radius: 9px;
background: #fff6ed;
color: #7a2e0e;
}
.item-list-card {
min-height: 520px;
}
.item-list-card > .card-body {
padding: 0;
}
.item-list-card .dataTables_wrapper {
padding: 12px;
}
.count-badge {
padding: 4px 9px;
background: #eef2f6;
border-radius: 12px;
font-size: 11px;
}
.invoice-summary {
margin-left: auto;
width: min(430px, 100%);
padding: 14px 18px;
}
.invoice-summary > div {
display: flex;
justify-content: space-between;
padding: 5px 0;
}
.invoice-summary .grand {
margin-top: 6px;
padding-top: 10px;
border-top: 2px solid #e4e7ec;
font-size: 17px;
}
.editor-actions {
position: sticky;
bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 12px;
padding: 13px 15px;
background: #fff;
border: 1px solid #e4e7ec;
border-radius: 12px;
box-shadow: 0 8px 25px rgba(16, 24, 40, 0.12);
z-index: 4;
}
.editor-actions small {
display: block;
color: #667085;
}
.editor-actions > div:last-child {
display: flex;
gap: 7px;
flex-wrap: wrap;
}
.money-input {
text-align: right;
}
.barcode-cell {
max-width: 220px;
white-space: normal;
}
.barcode-cell span {
display: block;
font-size: 11px;
}
.dataTables_processing {
z-index: 5;
}
.select2-container {
width: 100% !important;
}
@media (max-width: 1199px) {
.invoice-filters {
grid-template-columns: repeat(3, 1fr);
}
.invoice-editor-grid,
.invoice-editor-grid.is-creating {
grid-template-columns: 40% 60%;
}
}
@media (max-width: 991px) {
.invoice-editor-grid,
.invoice-editor-grid.is-creating {
grid-template-columns: 1fr;
}
.invoice-editor-left {
order: 1;
}
.invoice-editor-right {
order: 2;
}
.invoice-metrics {
grid-template-columns: repeat(2, 1fr);
}
.invoice-progress {
overflow-x: auto;
grid-template-columns: repeat(4, minmax(155px, 1fr));
}
.item-list-card {
min-height: 360px;
}
}
@media (max-width: 767px) {
.invoice-workspace {
padding: 12px;
}
.invoice-hero {
flex-direction: column;
}
.hero-actions,
.hero-actions .btn {
width: 100%;
}
.invoice-filters {
display: none;
grid-template-columns: 1fr 1fr;
}
.invoice-filters.open {
display: grid;
}
.filter-action {
grid-column: 1/-1;
}
.invoice-metrics {
grid-template-columns: 1fr 1fr;
}
.invoice-metrics article {
padding: 12px;
}
.invoice-metrics strong {
font-size: 16px;
}
.line-type-switch {
grid-template-columns: 1fr;
}
.editor-actions {
align-items: stretch;
flex-direction: column;
bottom: 66px;
}
.editor-actions > div:last-child,
.editor-actions .btn,
.editor-actions a {
width: 100%;
}
.invoice-rule-list > div {
flex-direction: column;
gap: 2px;
}
.invoice-rule-list dd {
text-align: left;
}
.modal-dialog {
margin: 0;
max-width: none;
height: 100%;
}
.modal-content {
min-height: 100%;
border-radius: 0;
}
}
@media (max-width: 767px) {
#invoiceTable thead,
#invoiceLinesTable thead {
display: none;
}
#invoiceTable,
#invoiceTable tbody,
#invoiceTable tr,
#invoiceTable td,
#invoiceLinesTable,
#invoiceLinesTable tbody,
#invoiceLinesTable tr,
#invoiceLinesTable td {
display: block;
width: 100% !important;
}
#invoiceTable tbody tr,
#invoiceLinesTable tbody tr {
margin: 10px 0;
padding: 8px;
border: 1px solid #e4e7ec;
border-radius: 11px;
background: #fff;
}
#invoiceTable tbody td,
#invoiceLinesTable tbody td {
position: relative;
min-height: 34px;
padding: 7px 8px 7px 42% !important;
border: 0;
border-bottom: 1px dashed #eaecf0;
text-align: left !important;
white-space: normal;
}
#invoiceTable tbody td:last-child,
#invoiceLinesTable tbody td:last-child {
border-bottom: 0;
}
#invoiceTable tbody td:before,
#invoiceLinesTable tbody td:before {
content: attr(data-label);
position: absolute;
left: 8px;
top: 8px;
width: 37%;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
color: #667085;
}
.invoice-table-actions {
min-width: 0;
}
.item-list-card .dataTables_wrapper {
padding: 7px;
}
}
+3
View File
@@ -0,0 +1,3 @@
.receivable-workspace{padding:20px;background:#f4f6f9;min-height:calc(100vh - 72px);color:#1d2939}.receivable-grid{display:grid;grid-template-columns:minmax(350px,42%) minmax(0,58%);gap:14px}.receivable-card{border:1px solid #e4e7ec;border-radius:13px;box-shadow:0 5px 20px rgba(16,24,40,.04);overflow:hidden}.receivable-card>.card-header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;background:#fff}.receivable-card>.card-header small{display:block;color:#667085}.receivable-card>.card-body{padding:16px}.allocation-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:14px 0 9px;color:#667085;font-size:12px}.allocation-toolbar>div{display:flex;gap:5px}.invoice-allocation-list{display:grid;gap:8px;max-height:560px;overflow:auto}.invoice-allocation{padding:12px;border:1px solid #e4e7ec;border-radius:10px;background:#fff}.invoice-allocation.is-overdue{border-color:#fda29b;background:#fffafa}.invoice-allocation-head{display:flex;justify-content:space-between;gap:10px}.invoice-allocation-head small,.invoice-allocation-meta{display:block;color:#667085}.invoice-allocation-meta{display:flex;justify-content:space-between;margin:8px 0;font-size:12px}.invoice-allocation-input{display:grid;grid-template-columns:1fr 150px;gap:8px;align-items:center;padding-top:8px;border-top:1px solid #eaecf0}.invoice-allocation-input input{text-align:right}.ar-empty{min-height:130px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:7px;color:#667085;text-align:center}.ar-empty i{font-size:29px;color:#98a2b3}.source-title{display:flex;justify-content:space-between;align-items:center;gap:10px;margin:17px 0 8px}.source-title small{display:block;color:#667085}.payment-source-row{display:grid;grid-template-columns:minmax(0,1fr) 160px 38px;gap:7px;margin-bottom:8px}.payment-source-row .source-amount{text-align:right}.remove-payment-source{border:1px solid #fda29b;border-radius:8px;background:#fff;color:#d92d20}.payment-balance{margin-top:13px;padding:12px;border:1px solid #e4e7ec;border-radius:10px;background:#f8fafc}.payment-balance>div{display:flex;justify-content:space-between;padding:4px}.payment-balance .difference{margin-top:4px;padding-top:9px;border-top:1px solid #d0d5dd}.payment-balance .is-negative{color:#d92d20}.payment-balance .is-positive{color:#b54708}.payment-attachment-grid{display:flex;flex-wrap:wrap;gap:9px}.attachment-add,.payment-file-tile{position:relative;width:92px;height:92px;border:1px dashed #98a2b3;border-radius:10px;background:#fff;overflow:visible}.attachment-add{display:flex;align-items:center;justify-content:center;flex-direction:column;color:#475467}.payment-file-tile{border-style:solid}.payment-file-tile img{width:100%;height:100%;object-fit:cover;border-radius:9px}.payment-file-tile .pdf-icon{height:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;color:#d92d20}.payment-file-tile .pdf-icon i{font-size:28px}.payment-file-tile .file-remove{position:absolute;z-index:3;right:-7px;top:-7px;width:23px;height:23px;border:2px solid #fff;border-radius:50%;background:#d92d20;color:#fff;line-height:17px}.payment-file-open{position:absolute;inset:0;border:0;background:transparent}.payment-file-tile small{position:absolute;bottom:2px;left:3px;right:3px;padding:2px;background:rgba(255,255,255,.85);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:9px}.payment-history-list{display:grid;gap:8px}.payment-history-row{display:grid;grid-template-columns:160px minmax(0,1fr) 150px auto;gap:12px;align-items:center;padding:11px;border:1px solid #e4e7ec;border-radius:9px}.payment-history-row small{display:block;color:#667085}#paymentPreviewModal iframe,#paymentPreviewModal img{display:none;width:100%;height:min(84vh,920px);border:0;object-fit:contain;background:#1d2939}#paymentPreviewModal .modal-content{max-height:94vh}
@media(max-width:991px){.receivable-grid{grid-template-columns:1fr}.invoice-allocation-list{max-height:none}}
@media(max-width:767px){.receivable-workspace{padding:12px}.allocation-toolbar{align-items:flex-start;flex-direction:column}.invoice-allocation-input{grid-template-columns:1fr}.payment-source-row{grid-template-columns:minmax(0,1fr) 110px 38px}.payment-history-row{grid-template-columns:1fr}.receivable-card table{min-width:800px}#paymentPreviewModal .modal-dialog{margin:0;max-width:none;height:100%}#paymentPreviewModal .modal-content{min-height:100%;border-radius:0}}
+267
View File
@@ -0,0 +1,267 @@
.sales-page {
padding: 20px;
background: #f4f6f9;
min-height: calc(100vh - 70px);
}
.sales-hero,
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.sales-hero span {
font-size: 11px;
letter-spacing: 0.14em;
color: #667085;
}
.sales-hero h2 {
margin: 3px 0;
}
.sales-hero p,
.card-header small,
td small {
display: block;
color: #667085;
}
.sales-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin: 18px 0;
}
.sales-metrics article {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
}
.sales-metrics strong {
display: block;
font-size: 20px;
margin: 5px 0;
}
.sales-metrics span {
font-size: 12px;
color: #667085;
}
.sales-tabs {
display: flex;
gap: 8px;
background: #fff;
border: 1px solid #e5e7eb;
padding: 7px;
border-radius: 12px;
margin-bottom: 15px;
overflow: auto;
}
.sales-tabs button,
.sales-tabs a {
border: 0;
background: transparent;
color: #475467;
padding: 10px 14px;
border-radius: 8px;
white-space: nowrap;
text-decoration: none;
}
.sales-tabs .active {
background: #0d6efd;
color: #fff;
}
.sales-tabs b {
background: #f79009;
color: #fff;
border-radius: 10px;
padding: 1px 6px;
margin-left: 5px;
}
.sales-page .card {
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 5px 18px rgba(16, 24, 40, 0.04);
}
.sales-page .card-header {
background: #fff;
padding: 15px;
}
.sales-page th {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #667085;
background: #f8fafc;
}
.actions {
min-width: 260px;
}
.actions .btn {
margin: 2px;
}
.status {
display: inline-block;
padding: 4px 8px;
border-radius: 14px;
font-size: 11px;
font-weight: 700;
background: #eef2f6;
}
.status-posted,
.status-completed {
background: #dcfae6;
color: #067647;
}
.status-submitted,
.status-received {
background: #fef0c7;
color: #b54708;
}
.status-draft {
background: #e0f2fe;
color: #026aa2;
}
.empty {
padding: 40px !important;
text-align: center;
color: #667085;
}
.modal-dialog-scrollable .modal-content {
max-height: 92vh;
}
.invoice-head,
.line-builder {
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 14px;
margin-bottom: 15px;
}
.line-builder h6 {
margin-bottom: 12px;
}
.barcode-choices {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
}
.barcode-choices label {
border: 1px solid #d0d5dd;
border-radius: 8px;
padding: 6px 10px;
}
.invoice-total {
display: flex;
justify-content: flex-end;
gap: 30px;
border-top: 2px solid #e5e7eb;
padding: 14px;
font-size: 18px;
}
.payment-balance {
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 9px;
padding: 12px;
margin-top: 12px;
}
.payment-balance div {
display: flex;
justify-content: space-between;
}
.file-picker {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.file-picker > label,
.file-thumb {
width: 84px;
height: 84px;
border: 1px dashed #98a2b3;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
overflow: visible;
}
.file-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 9px;
}
.file-thumb button {
position: absolute;
right: -7px;
top: -7px;
border: 0;
border-radius: 50%;
background: #dc3545;
color: #fff;
width: 23px;
height: 23px;
z-index: 2;
}
#pdfModal iframe {
width: 100%;
height: min(82vh, 900px);
border: 0;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.detail-card {
border: 1px solid #e5e7eb;
border-radius: 10px;
padding: 14px;
}
.detail-card.wide {
grid-column: 1/-1;
}
.return-row {
border: 1px solid #e5e7eb;
border-radius: 9px;
padding: 10px;
margin-bottom: 8px;
}
.money-field {
text-align: right;
}
@media (max-width: 767px) {
.sales-page {
padding: 12px;
}
.sales-hero {
align-items: flex-start;
}
.sales-metrics {
grid-template-columns: repeat(2, 1fr);
}
.sales-tabs button,
.sales-tabs a {
padding: 9px;
}
.modal-dialog {
margin: 0;
max-width: none;
height: 100%;
}
.modal-content {
min-height: 100%;
border-radius: 0;
}
.detail-grid {
grid-template-columns: 1fr;
}
.actions {
min-width: 220px;
}
.sales-page td,
.sales-page th {
white-space: nowrap;
}
}
+121 -1124
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
.tech-shell{--tech-navy:#172845;--tech-orange:#f47c00;--tech-border:#dfe5ee;--tech-muted:#68748a}.tech-subnav{display:flex;gap:.45rem;overflow-x:auto;background:#fff;border:1px solid var(--tech-border);border-radius:14px;padding:.5rem;margin-bottom:1rem;scrollbar-width:thin}.tech-subnav a{display:flex;align-items:center;gap:.45rem;white-space:nowrap;text-decoration:none;color:#41506a;padding:.65rem .85rem;border-radius:9px;font-weight:600}.tech-subnav a.active{background:var(--tech-navy);color:#fff}.tech-subnav .badge{background:#dc3545}.tech-summary{display:grid;grid-template-columns:repeat(6,minmax(120px,1fr));gap:.75rem}.tech-stat,.tech-card{background:#fff;border:1px solid var(--tech-border);border-radius:14px;box-shadow:0 5px 18px rgba(31,45,72,.045)}.tech-stat{padding:1rem}.tech-stat small{display:block;color:var(--tech-muted);margin-bottom:.25rem}.tech-stat strong{font-size:1.5rem;color:var(--tech-navy)}.tech-card{padding:1rem;margin-top:1rem}.tech-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1rem}.tech-card-head h5{margin:0;color:#202b3c}.tech-card-head p{margin:.2rem 0 0;color:var(--tech-muted);font-size:.88rem}.tech-scan{display:grid;grid-template-columns:1fr auto;gap:.65rem}.tech-scan input{min-height:48px;font-size:1rem}.tech-scan-result{border:1px dashed #bdc9da;border-radius:12px;padding:1rem;margin-top:1rem}.tech-identity{display:grid;grid-template-columns:2fr repeat(4,1fr);gap:.75rem}.tech-kv{background:#f7f9fc;border-radius:9px;padding:.7rem}.tech-kv small{display:block;color:var(--tech-muted)}.tech-actions{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.8rem}.tech-actions .btn{min-height:40px}.tech-two{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:1rem}.tech-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem}.tech-form-grid .full{grid-column:1/-1}.tech-table-wrap{overflow-x:auto}.tech-table{min-width:760px;margin:0}.tech-table th{font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:#5d6980;background:#f6f8fb}.tech-empty{text-align:center;color:var(--tech-muted);padding:2rem}.tech-file-box{display:flex;gap:.65rem;flex-wrap:wrap}.tech-add-file,.tech-thumb{width:82px;height:82px;border-radius:10px}.tech-add-file{display:grid;place-items:center;border:2px dashed #bdc7d6;background:#fff;color:#506078;cursor:pointer}.tech-thumb{position:relative;border:1px solid #dce2eb;background:#f4f6f9;overflow:visible}.tech-thumb img,.tech-thumb .tech-pdf{width:100%;height:100%;object-fit:cover;border-radius:9px;display:grid;place-items:center;cursor:pointer}.tech-thumb button{position:absolute;right:-7px;top:-7px;width:24px;height:24px;border:0;border-radius:50%;background:#dc3545;color:#fff;z-index:3;line-height:1}.tech-modal-summary{display:grid;grid-template-columns:repeat(3,1fr);gap:.7rem}.tech-modal-summary>div{border:1px solid var(--tech-border);border-radius:9px;padding:.65rem}.tech-modal-summary small{display:block;color:var(--tech-muted)}.tech-owner-customer{color:#7c3aed}.tech-owner-company{color:#087f5b}.tech-local-loading{position:absolute;inset:0;background:rgba(255,255,255,.82);display:none;z-index:5;place-items:center;border-radius:14px}.tech-card.is-loading{position:relative}.tech-card.is-loading .tech-local-loading{display:grid}.tech-step-label{font-size:.75rem;font-weight:700;color:var(--tech-orange);letter-spacing:.06em;text-transform:uppercase}.tech-status{display:inline-flex;padding:.2rem .5rem;border-radius:999px;background:#eaf1ff;color:#2456a6;font-size:.75rem}.tech-warning{background:#fff7df;border:1px solid #ffe2a2;border-radius:10px;padding:.75rem;color:#72520b}.tech-check{display:flex;gap:.5rem;align-items:flex-start}.tech-check input{margin-top:.28rem}.tech-doc-actions{display:flex;gap:.35rem}.tech-doc-actions .btn{white-space:nowrap}
@media(max-width:1100px){.tech-summary{grid-template-columns:repeat(3,1fr)}.tech-identity{grid-template-columns:repeat(3,1fr)}}
@media(max-width:767.98px){.tech-summary{grid-template-columns:repeat(2,1fr)}.tech-two,.tech-form-grid{grid-template-columns:1fr}.tech-identity,.tech-modal-summary{grid-template-columns:1fr 1fr}.tech-card{padding:.8rem}.tech-card-head{display:block}.tech-card-head .btn{margin-top:.6rem;width:100%}.tech-scan{grid-template-columns:1fr}.tech-scan .btn{min-height:46px}.modal.tech-modal .modal-dialog{margin:0;max-width:none;height:100%}.modal.tech-modal .modal-content{min-height:100%;border:0;border-radius:0}.modal.tech-modal .modal-body{overflow:auto}.tech-actions .btn{flex:1 1 46%}}
@media(max-width:420px){.tech-identity,.tech-modal-summary{grid-template-columns:1fr}.tech-summary{grid-template-columns:1fr 1fr}.tech-stat{padding:.75rem}.tech-stat strong{font-size:1.15rem}}
.tech-bulk-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.75rem}.tech-bulk-action{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto;column-gap:.75rem;text-align:left;align-items:center;border:1px solid var(--tech-border);border-radius:12px;background:#fff;color:#33435d;padding:.9rem;min-height:78px}.tech-bulk-action i{grid-row:1/3;font-size:1.45rem;color:var(--tech-orange)}.tech-bulk-action strong,.tech-bulk-action small{display:block}.tech-bulk-action small{color:var(--tech-muted)}.tech-bulk-action.active{border-color:var(--tech-navy);background:var(--tech-navy);color:#fff;box-shadow:0 8px 20px rgba(23,40,69,.15)}.tech-bulk-action.active small,.tech-bulk-action.active i{color:#fff}.tech-search-results{margin-top:.6rem;border:1px solid var(--tech-border);border-radius:12px;background:#fff;box-shadow:0 12px 26px rgba(22,38,65,.1);overflow:hidden;max-height:320px;overflow-y:auto}.tech-search-option{width:100%;border:0;border-bottom:1px solid #edf0f5;background:#fff;padding:.75rem .9rem;display:flex;align-items:center;justify-content:space-between;gap:1rem;text-align:left}.tech-search-option:last-child{border-bottom:0}.tech-search-option:hover,.tech-search-option:focus{background:#f2f6fd}.tech-search-option span,.tech-search-option small{display:block}.tech-search-option>span:last-child{text-align:right;white-space:nowrap}.tech-search-option small{color:var(--tech-muted)}.tech-bulk-submit{position:sticky;bottom:.75rem;z-index:10;margin-top:1rem;padding:.85rem 1rem;border:1px solid #ccd5e2;border-radius:14px;background:rgba(255,255,255,.96);box-shadow:0 10px 28px rgba(17,31,52,.13);display:flex;align-items:center;justify-content:space-between;gap:1rem;backdrop-filter:blur(8px)}.tech-bulk-submit small{display:block;color:var(--tech-muted)}
@media(max-width:767.98px){.tech-bulk-actions{grid-template-columns:1fr}.tech-bulk-submit{bottom:0;border-radius:12px 12px 0 0;display:block}.tech-bulk-submit .btn{width:100%;margin-top:.65rem}}
.tech-search-option{color:#33435d;text-decoration:none}.tech-search-option:hover{color:#172845}
.tech-technician-stock{border:1px solid var(--tech-border);border-radius:12px;background:#fff;overflow:hidden}.tech-stock-toolbar,.tech-selected-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:.75rem .9rem}.tech-stock-toolbar{background:#f7f9fc;border-bottom:1px solid var(--tech-border)}.tech-stock-toolbar small,.tech-selected-head small{display:block;color:var(--tech-muted)}.tech-stock-list{max-height:360px;overflow:auto}.tech-stock-list thead{position:sticky;top:0;z-index:2}.tech-stock-check{width:1.1rem;height:1.1rem;cursor:pointer}.tech-stock-row-selected{background:#f0f7ff!important}
@media(max-width:767.98px){.tech-stock-toolbar,.tech-selected-head{align-items:flex-start;flex-direction:column}.tech-stock-toolbar>div:last-child{width:100%}.tech-stock-toolbar .btn{flex:1}}
.tech-inline-search{display:flex;gap:.5rem}.tech-inline-search .form-control{min-width:0}.tech-selection-info{min-height:42px;border:1px solid var(--tech-border);border-radius:10px;background:#f7f9fc;padding:.6rem .75rem;color:#526078;font-size:.85rem}.tech-selection-info strong{color:var(--tech-navy)}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

+114
View File
@@ -0,0 +1,114 @@
(function (window, document) {
'use strict';
var nativeFetch = window.fetch.bind(window);
function toast(text) {
var old = document.querySelector('.ajax-error-toast');
if (old) old.remove();
var element = document.createElement('div');
element.className = 'ajax-error-toast';
element.setAttribute('role', 'alert');
element.textContent = text;
document.body.appendChild(element);
window.setTimeout(function () { element.remove(); }, 7000);
}
function addCsrf(formData) {
var token = window.APP_CSRF;
if (token && token.name && !formData.has(token.name)) {
formData.append(token.name, token.value);
}
return formData;
}
function secureFetch(url, options) {
options = Object.assign({}, options || {});
var method = String(options.method || 'GET').toUpperCase();
var token = window.APP_CSRF;
var target;
try {
target = new URL(url, window.location.href);
} catch (_error) {
target = null;
}
if (target && target.origin === window.location.origin && !/^(GET|HEAD|OPTIONS)$/.test(method) && token && token.name) {
if (options.body instanceof FormData) {
addCsrf(options.body);
} else if (options.body instanceof URLSearchParams) {
options.body = new URLSearchParams(options.body);
if (!options.body.has(token.name)) options.body.append(token.name, token.value);
}
}
return nativeFetch(url, options);
}
window.fetch = secureFetch;
async function request(url, options) {
options = options || {};
options.headers = Object.assign({
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}, options.headers || {});
if (options.body instanceof FormData) addCsrf(options.body);
var response;
try {
response = await window.fetch(url, options);
} catch (error) {
throw new Error('Tidak dapat terhubung ke server. Periksa jaringan atau Apache.');
}
var contentType = response.headers.get('content-type') || '';
if (contentType.indexOf('application/json') === -1) {
var raw = await response.text();
if (response.redirected || /<(?:html|!doctype)/i.test(raw)) {
throw new Error('Sesi berakhir atau server mengirim halaman error. Silakan login ulang.');
}
throw new Error(raw.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 240) || 'Respons server tidak valid.');
}
var payload = await response.json();
if (!response.ok) throw new Error(payload.message || ('Permintaan gagal (HTTP ' + response.status + ').'));
return payload;
}
window.App = Object.assign(window.App || {}, {
request: request,
addCsrf: addCsrf,
showError: toast
});
if (window.jQuery) {
window.jQuery.ajaxSetup({headers: {'X-Requested-With': 'XMLHttpRequest'}});
window.jQuery.ajaxPrefilter(function (options, originalOptions) {
var method = String(options.type || options.method || 'GET').toUpperCase();
var token = window.APP_CSRF;
if (/^(GET|HEAD|OPTIONS)$/.test(method) || !token || !token.name) return;
if (options.data instanceof FormData) {
if (!options.data.has(token.name)) options.data.append(token.name, token.value);
return;
}
if (typeof options.data === 'string') {
var encodedName = encodeURIComponent(token.name);
if (options.data.indexOf(encodedName + '=') === -1) {
options.data += (options.data ? '&' : '') + encodedName + '=' + encodeURIComponent(token.value);
}
return;
}
options.data = window.jQuery.extend({}, originalOptions.data || options.data || {});
if (typeof options.data[token.name] === 'undefined') options.data[token.name] = token.value;
});
window.jQuery(document).ajaxError(function (_event, xhr, _settings, error) {
var message = 'Pemanggilan data gagal';
if (xhr.status === 401 || xhr.status === 403) message = 'Sesi atau hak akses tidak valid. Silakan login ulang.';
else if (xhr.status) message += ' (HTTP ' + xhr.status + ').';
else message += '. Periksa koneksi atau Apache.';
toast(error && error !== 'error' ? error : message);
});
}
}(window, document));
+17
View File
@@ -0,0 +1,17 @@
(function(){'use strict';var d=document,q=function(s,c){return Array.prototype.slice.call((c||d).querySelectorAll(s));};
window.cleanNumber=function(v){if(v===null||v===undefined)return'';var s=String(v).trim().replace(/\s|Rp/gi,'');if(!s)return'';var neg=s.charAt(0)==='-';s=s.replace(/-/g,'');var comma=s.lastIndexOf(','),dot=s.lastIndexOf('.');if(comma>=0&&dot>=0){if(comma>dot)s=s.replace(/\./g,'').replace(',','.');else s=s.replace(/,/g,'');}else if(comma>=0){var ca=s.split(',');s=(ca.length===2&&ca[1].length>0&&ca[1].length<=2)?ca[0].replace(/,/g,'')+'.'+ca[1]:s.replace(/,/g,'');}else if(dot>=0){var da=s.split('.');s=(da.length===2&&da[1].length>0&&da[1].length<=2)?s:s.replace(/\./g,'');}s=s.replace(/[^0-9.]/g,'');return(neg?'-':'')+s;};
window.parseMoney=function(v){var n=Number(window.cleanNumber(v));return Number.isFinite(n)?n:0;};
window.formatNumber=function(v){if(v===null||v===undefined)return'';var raw=String(v).trim().replace(/\s|Rp/gi,''),neg=raw.charAt(0)==='-';raw=raw.replace(/-/g,'').replace(/[^0-9.,]/g,'');if(raw==='')return'';var comma=raw.lastIndexOf(','),dot=raw.lastIndexOf('.'),integer=raw,decimal='',hasDecimal=false;if(comma>=0&&dot>=0){var sep=comma>dot?',':'.',at=Math.max(comma,dot);integer=raw.substring(0,at).replace(/[.,]/g,'');decimal=raw.substring(at+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else if(comma>=0){var ctail=raw.length-comma-1;if(ctail<=2){integer=raw.substring(0,comma).replace(/\D/g,'');decimal=raw.substring(comma+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else integer=raw.replace(/,/g,'');}else if(dot>=0){var dtail=raw.length-dot-1;if(raw.split('.').length===2&&dtail>0&&dtail<=2){integer=raw.substring(0,dot).replace(/\D/g,'');decimal=raw.substring(dot+1).replace(/\D/g,'').substring(0,2);hasDecimal=true;}else integer=raw.replace(/\./g,'');}integer=(integer||'0').replace(/^0+(?=\d)/,'').replace(/\B(?=(\d{3})+(?!\d))/g,'.');return(neg?'-':'')+integer+(hasDecimal?','+decimal:'');};
window.formatRupiah=function(v){return'Rp '+new Intl.NumberFormat('id-ID').format(Number(v)||0);};
window.formatTglIndo=function(v){if(!v)return'-';var x=String(v).split(' ')[0].split('-');if(x.length!==3)return v;var b=['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];return Number(x[2])+' '+b[Number(x[1])-1]+' '+x[0];};
function filter(v){v=(v||'').toLowerCase();q('.app-menu-item').forEach(function(x){x.hidden=!!v&&x.dataset.menuLabel.indexOf(v)<0;if(v&&x.tagName==='DETAILS'&&!x.hidden)x.open=true;});var box=d.querySelector('.search-menu-results');if(!box)return;box.innerHTML='';q('.app-sidebar .app-menu-item:not([hidden]) a[href]').slice(0,16).forEach(function(a){var n=a.cloneNode(true);n.className='d-block p-2 border-bottom text-decoration-none';box.appendChild(n);});if(!box.children.length){var empty=d.createElement('div');empty.className='empty-state';empty.textContent='Menu tidak ditemukan';box.appendChild(empty);}}
q('.menu-filter').forEach(function(x){x.addEventListener('input',function(){filter(x.value);});});
function moneyFields(scope){q('input[name],input.format-rupiah',scope||d).forEach(function(x){var n=String(x.name||x.id||'').replace(/\[[^\]]*\]/g,'').toLowerCase();var money=/^(amount|debit|kredit|nominal|jumlah|harga|price|unit_price|estimated_price|discount_amount|opening_balance|statement_balance|outstanding_receipts|outstanding_payments|charge|credit_limit|nilai|nilai_perolehan|nilai_residu|biaya|budget|basic_salary|salary|fixed_amount|installment_amount|principal|residual_value|proceeds)$/.test(n)||/(^|_)(amount|nominal|harga|salary|balance|cost|budget|price|proceeds)$/.test(n);if(!money)return;x.classList.add('format-rupiah');if(String(x.type).toLowerCase()==='number')x.type='text';x.setAttribute('inputmode','decimal');if(x.value)x.value=window.formatNumber(x.value);});}
moneyFields(d);q('input[type=number],.format-rupiah').forEach(function(x){x.setAttribute('inputmode',x.step&&String(x.step).indexOf('.')>=0?'decimal':'numeric');});
q('img:not([loading])').forEach(function(x){x.loading='lazy';x.decoding='async';});
q('.table-responsive table').forEach(function(t){t.classList.add('table-hover','responsive-detail-table');var heads=q('thead th',t).map(function(h){return h.textContent.trim();});q('tbody tr',t).forEach(function(r){q('td',r).forEach(function(c,i){if(!c.dataset.label)c.dataset.label=heads[i]||'';});});});
q('form').forEach(function(f){f.addEventListener('submit',function(e){if(e.defaultPrevented||f.dataset.ajax==='true')return;if(f.dataset.submitting==='1'){e.preventDefault();return;}if(!navigator.onLine&&String(f.method).toLowerCase()==='post'){e.preventDefault();alert('Posting transaksi offline tidak diizinkan. Sambungkan internet lalu coba kembali.');return;}f.dataset.submitting='1';q('button[type=submit],input[type=submit]',f).forEach(function(b){b.disabled=true;b.setAttribute('aria-busy','true');});var l=d.getElementById('globalLoading');if(l)l.hidden=false;});});
q('input[type=file][accept*="image"]:not([multiple])').forEach(function(input){input.addEventListener('change',function(){var file=input.files&&input.files[0];if(!file||file.size<350000)return;var img=new Image(),url=URL.createObjectURL(file);img.onload=function(){var scale=Math.min(1,1600/Math.max(img.width,img.height)),canvas=d.createElement('canvas');canvas.width=Math.round(img.width*scale);canvas.height=Math.round(img.height*scale);canvas.getContext('2d').drawImage(img,0,0,canvas.width,canvas.height);canvas.toBlob(function(blob){if(blob&&blob.size<file.size&&window.DataTransfer){var dt=new DataTransfer();dt.items.add(new File([blob],file.name.replace(/\.[^.]+$/,'.jpg'),{type:'image/jpeg'}));input.files=dt.files;}URL.revokeObjectURL(url);},'image/jpeg',.82);};img.src=url;});});
if(window.jQuery){jQuery(d).on('input','.format-rupiah',function(){this.value=window.formatNumber(this.value);}).on('submit','form',function(){jQuery(this).find('.format-rupiah').each(function(){this.value=window.cleanNumber(this.value);});});var initSelect=function(scope){if(!jQuery.fn.select2)return;jQuery(scope||d).find('select.select-search').each(function(){var x=jQuery(this);if(x.hasClass('select2-hidden-accessible'))return;var p=x.closest('.modal'),lookup=x.data('lookup'),options={width:'100%',placeholder:x.data('placeholder')||x.attr('placeholder')||'-- Pilih --',allowClear:true,dropdownParent:p.length?p:jQuery(document.body)};if(lookup)options.ajax={url:(window.APP_BASE_URL||'/')+'lookups/search/'+lookup,dataType:'json',delay:300,cache:true,data:function(params){return{q:params.term||'',page:params.page||1,limit:20}},processResults:function(data){return data}};x.select2(options);});};initSelect(d);if(window.MutationObserver)new MutationObserver(function(ms){ms.forEach(function(m){Array.prototype.forEach.call(m.addedNodes,function(n){if(n.nodeType===1){moneyFields(n);initSelect(n);}});});}).observe(d.body,{childList:true,subtree:true});}
window.addEventListener('pageshow',function(){var l=d.getElementById('globalLoading');if(l)l.hidden=true;q('form').forEach(function(f){delete f.dataset.submitting;q('[aria-busy]',f).forEach(function(b){b.disabled=false;b.removeAttribute('aria-busy');});});});
if('serviceWorker'in navigator)window.addEventListener('load',function(){navigator.serviceWorker.register((window.APP_BASE_URL||'/')+'service-worker.js');});}());
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
(function($){
'use strict';
var cfg=window.RECEIVABLE_PAGE||{},invoices=[],files=[],sourceIndex=0;
function esc(v){return $('<div>').text(v==null?'':v).html()}
function parseMoney(v){v=String(v==null?'':v).replace(/Rp|\s/g,'');if(v.indexOf(',')>=0)v=v.replace(/\./g,'').replace(',','.');else if(/^\d{1,3}(\.\d{3})+$/.test(v))v=v.replace(/\./g,'');return Number(v)||0}
function money(v){return'Rp '+new Intl.NumberFormat('id-ID',{minimumFractionDigits:2,maximumFractionDigits:2}).format(Number(v)||0)}
function fmt(v){return new Intl.NumberFormat('id-ID',{minimumFractionDigits:0,maximumFractionDigits:2}).format(Number(v)||0)}
function toast(message,icon){if(window.AppAlert)return AppAlert.toast(message,icon||'success');return Swal.fire({toast:true,position:'top-end',icon:icon||'success',title:message,showConfirmButton:false,timer:3200})}
function fail(xhr){var m='Permintaan tidak dapat diproses.';try{var r=xhr.responseJSON||JSON.parse(xhr.responseText);m=r.message||m}catch(e){}toast(m,'error')}
function select2(selector,url,placeholder){$(selector).select2({width:'100%',placeholder:placeholder,allowClear:true,ajax:{url:cfg.base+url,dataType:'json',delay:300,data:function(p){return{q:p.term||'',page:p.page||1}},processResults:function(r){return{results:r.results||[],pagination:r.pagination||{more:false}}},cache:true}})}
function addSource(){sourceIndex++;var row=$('<div class="payment-source-row"><select class="form-select source-account"></select><input class="form-control source-amount" inputmode="decimal" placeholder="Nominal"><button type="button" class="remove-payment-source" aria-label="Hapus sumber"><i class="bi bi-x-lg"></i></button></div>');$('#paymentSources').append(row);select2(row.find('.source-account'),'receivables/lookup_cash_accounts','Pilih akun Aktiva / Passiva');row.find('.source-amount').on('input blur',function(e){if(e.type==='blur')this.value=fmt(parseMoney(this.value));calculate()})}
function renderInvoices(rows){invoices=Array.isArray(rows)?rows:[];if(!invoices.length){$('#invoiceAllocations').html('<div class="ar-empty"><i class="bi bi-receipt-cutoff"></i><span>Customer ini tidak memiliki invoice terbuka.</span></div>');$('#invoiceSelectionHint').text('Tidak ada invoice terbuka.');calculate();return}var today=new Date().toISOString().slice(0,10);$('#invoiceAllocations').html(invoices.map(function(i){var overdue=i.jatuh_tempo<today;return'<article class="invoice-allocation '+(overdue?'is-overdue':'')+'"><div class="invoice-allocation-head"><div><b>'+esc(i.no_invoice)+'</b><small>'+esc(i.invoice_type==='running'?'Berjalan / Akumulasi':'Sekali transaksi')+'</small></div><span class="badge '+(overdue?'bg-danger':'bg-warning text-dark')+'">'+(overdue?'Jatuh tempo':'Belum jatuh tempo')+'</span></div><div class="invoice-allocation-meta"><span>Total '+money(i.total)+'</span><span>Terbayar '+money(i.total_bayar)+'</span></div><div class="invoice-allocation-input"><b>Sisa '+money(i.sisa_piutang)+'</b><input class="form-control allocation-amount" data-id="'+Number(i.id)+'" data-balance="'+Number(i.sisa_piutang)+'" inputmode="decimal" value="0" aria-label="Alokasi '+esc(i.no_invoice)+'"></div></article>'}).join(''));$('#invoiceSelectionHint').text(invoices.length+' invoice belum lunas.');$('.allocation-amount').on('input blur',function(e){var value=Math.min(parseMoney(this.value),Number($(this).data('balance')));if(e.type==='blur')this.value=fmt(value);calculate()});if(cfg.focus&&Number(cfg.focus.customer_id)===Number($('#payCustomer').val())){$('.allocation-amount[data-id="'+Number(cfg.focus.id)+'"]').val(fmt(cfg.focus.sisa_piutang));cfg.focus=null}calculate()}
function loadCustomer(id){$('#paymentCustomerId').val(id||'');if(!id){renderInvoices([]);return}$('#invoiceAllocations').html('<div class="ar-empty"><span class="spinner-border spinner-border-sm"></span><span>Memuat invoice...</span></div>');$.getJSON(cfg.base+'receivables/open_invoices/'+id).done(function(r){if(!r.status)return toast(r.message,'error');renderInvoices(r.invoices)}).fail(fail);$.getJSON(cfg.base+'receivables/payment_history/'+id).done(function(r){renderHistory(r.payments||[])}).fail(fail)}
function renderHistory(rows){if(!rows.length){$('#paymentHistory').html('<div class="ar-empty"><i class="bi bi-clock-history"></i><span>Belum ada riwayat pembayaran customer ini.</span></div>');return}$('#paymentHistory').html('<div class="payment-history-list">'+rows.map(function(p){return'<div class="payment-history-row"><div><b>'+esc(p.payment_no)+'</b><small>'+esc(p.tanggal)+'</small></div><div>'+esc((p.allocations||'Uang muka').replace(/\|\|/g,', '))+'<small>Referensi '+esc(p.reference_no||'-')+' · '+esc(p.creator_name||'-')+'</small></div><b>'+money(p.jumlah)+'</b><button class="btn btn-sm btn-outline-danger preview-receipt" data-url="'+cfg.base+'sales/receipt_pdf/'+p.id+'"><i class="bi bi-file-pdf"></i> Bukti</button></div>'}).join('')+'</div>')}
function calculate(){var allocated=0;$('.allocation-amount').each(function(){allocated+=parseMoney(this.value)});var source=0;$('.source-amount').each(function(){source+=parseMoney(this.value)});var diff=source-allocated;$('#allocatedTotal').text(money(allocated));$('#sourceTotal').text(money(source));$('#paymentDifference').text(money(diff)).toggleClass('is-negative',diff<-.001).toggleClass('is-positive',diff>.001);return{allocated:allocated,source:source,difference:diff}}
function syncFileInput(){var dt=new DataTransfer();files.forEach(function(f){dt.items.add(f)});document.getElementById('paymentAttachments').files=dt.files;renderFiles()}
function renderFiles(){$('#paymentAttachmentGrid .payment-file-tile').remove();var add=$('#addPaymentAttachment');files.forEach(function(f,i){var url=URL.createObjectURL(f),pdf=f.type==='application/pdf';var tile=$('<div class="payment-file-tile" data-index="'+i+'">'+(pdf?'<div class="pdf-icon"><i class="bi bi-file-earmark-pdf"></i></div>':'<img alt="'+esc(f.name)+'" src="'+url+'">')+'<button type="button" class="payment-file-open" aria-label="Pratinjau"></button><small>'+esc(f.name)+'</small><button type="button" class="file-remove" aria-label="Hapus">×</button></div>');tile.data('url',url).data('type',f.type);add.before(tile)})}
function preview(url,type){var modal=$('#paymentPreviewModal'),frame=modal.find('iframe'),img=modal.find('img');frame.hide().attr('src','about:blank');img.hide().attr('src','');if(type==='application/pdf')frame.show().attr('src',url);else img.show().attr('src',url);bootstrap.Modal.getOrCreateInstance(modal[0]).show()}
$(function(){select2('#payCustomer','receivables/lookup_customers','Cari customer');addSource();if(cfg.focus){var option=new Option(cfg.focus.customer_name,cfg.focus.customer_id,true,true);$('#payCustomer').append(option).trigger('change')}else $('#invoiceSelectionHint').text('Pilih customer terlebih dahulu.')});
$('#payCustomer').on('change',function(){loadCustomer(this.value)});$('#addPaymentSource').on('click',addSource);$(document).on('click','.remove-payment-source',function(){if($('.payment-source-row').length===1){$(this).closest('.payment-source-row').find('select').val(null).trigger('change');$(this).closest('.payment-source-row').find('input').val('');calculate();return}$(this).closest('.payment-source-row').remove();calculate()});$('#allocateAll').on('click',function(){$('.allocation-amount').each(function(){this.value=fmt($(this).data('balance'))});calculate()});$('#clearAllocations').on('click',function(){$('.allocation-amount').val('0');calculate()});$('#addPaymentAttachment').on('click',function(){$('#paymentAttachments').trigger('click')});$('#paymentAttachments').on('change',function(){files=files.concat(Array.from(this.files));if(files.length>10){files=files.slice(0,10);toast('Maksimal 10 bukti pembayaran.','warning')}syncFileInput()});$(document).on('click','.file-remove',function(e){e.stopPropagation();files.splice(Number($(this).closest('.payment-file-tile').data('index')),1);syncFileInput()});$(document).on('click','.payment-file-open',function(){var tile=$(this).closest('.payment-file-tile');preview(tile.data('url'),tile.data('type'))});$(document).on('click','.preview-receipt',function(){preview($(this).data('url'),'application/pdf')});
$('#paymentPreviewModal').on('hidden.bs.modal',function(){$(this).find('iframe').attr('src','about:blank');$(this).find('img').attr('src','')});
$('#paymentForm').on('submit',function(e){e.preventDefault();var customer=$('#payCustomer').val(),allocations={},sources=[];$('.allocation-amount').each(function(){var value=parseMoney(this.value);if(value>0)allocations[$(this).data('id')]=value});$('.payment-source-row').each(function(){var account=$(this).find('.source-account').val(),amount=parseMoney($(this).find('.source-amount').val());if(account&&amount>0)sources.push({account_id:Number(account),amount:amount})});var totals=calculate();if(!customer)return toast('Pilih customer.','warning');if(!sources.length)return toast('Tambahkan minimal satu sumber Kas/Bank dan nominal.','warning');if(!files.length)return toast('Bukti pembayaran wajib ditambahkan.','warning');if(totals.difference<-.001)return toast('Sumber penerimaan lebih kecil dari alokasi invoice.','warning');if(totals.difference>.001&&!$('#allowAdvance').is(':checked'))return toast('Terdapat kelebihan. Centang uang muka customer atau sesuaikan nominal.','warning');var form=this,button=$('#postPayment');var confirm=window.AppAlert?AppAlert.confirm({icon:'question',title:'Post pembayaran?',html:'Penerimaan <b>'+money(totals.source)+'</b> akan dialokasikan sebesar <b>'+money(totals.allocated)+'</b>.',confirmButtonText:'Ya, Post'}):Swal.fire({icon:'question',title:'Post pembayaran?',showCancelButton:true,confirmButtonText:'Ya, Post'});confirm.then(function(r){if(!r.isConfirmed)return;var fd=new FormData(form);fd.set('customer_id',customer);fd.set('sources',JSON.stringify(sources));fd.set('allocations_json',JSON.stringify(allocations));button.prop('disabled',true).html('<span class="spinner-border spinner-border-sm"></span> Memproses...');$.ajax({url:form.action,type:'POST',data:fd,dataType:'json',processData:false,contentType:false}).done(function(x){if(!x.status)return toast(x.message,'error');toast(x.message,'success');if(x.result&&x.result.payment_id)preview(cfg.base+'sales/receipt_pdf/'+x.result.payment_id,'application/pdf');loadCustomer(customer);files=[];syncFileInput();$('.source-amount').val('');calculate()}).fail(fail).always(function(){button.prop('disabled',false).html('<i class="bi bi-check-circle"></i> Post Pembayaran')})})});
})(jQuery);
File diff suppressed because it is too large Load Diff
+336
View File
@@ -0,0 +1,336 @@
(function ($) {
'use strict';
const cfg = window.TECH_EQUIPMENT_BULK || {};
const base = cfg.base || '/';
const form = document.getElementById('techBulkForm');
if (!form) return;
let action = '';
let selected = [];
let searchResults = [];
let technicianHoldings = [];
let visibleHoldings = [];
let holdingsRequest = null;
const fileSelections = new WeakMap();
const esc = value => $('<div>').text(value == null ? '' : String(value)).html();
const number = value => {
const n = Number(value || 0);
return Number.isInteger(n) ? n.toLocaleString('id-ID') : n.toLocaleString('id-ID', {maximumFractionDigits: 4});
};
const toast = (ok, message) => {
if (window.Swal) Swal.fire({toast: true, position: 'top-end', icon: ok ? 'success' : 'error', title: message, showConfirmButton: false, timer: ok ? 2600 : 4500, timerProgressBar: true});
else alert(message);
};
const key = () => 'TEQ-BATCH-' + Date.now() + '-' + Math.random().toString(36).slice(2);
function modal(id, show) {
const element = document.getElementById(id);
if (!element) return;
const instance = bootstrap.Modal.getOrCreateInstance(element, {backdrop: 'static', keyboard: false});
show ? instance.show() : instance.hide();
}
function initEmployeeSearch() {
if (!$.fn.select2) return;
$(form).find('select[name="technician_id"],select[name="target_technician_id"]').each(function () {
const select = $(this);
if (select.hasClass('select2-hidden-accessible')) return;
select.select2({width: '100%', placeholder: select.find('option:first').text() || 'Cari karyawan', allowClear: true});
});
}
function setAction(next, resetItems) {
action = next;
form.elements.action.value = action;
form.elements.idempotency_key.value = key();
document.querySelectorAll('.tech-bulk-action').forEach(button => button.classList.toggle('active', button.dataset.action === action));
const isTransfer = action === 'transfer';
const isReturn = action === 'return';
$('[data-bulk-field="target"]').toggleClass('d-none', !isTransfer).find('select').prop('required', isTransfer);
$('[data-bulk-field="warehouse"]').toggleClass('d-none', !isReturn).find('select').prop('required', false);
$('[data-bulk-field="proof"]').toggleClass('d-none', !isReturn);
$('[data-tech-label]').text(action === 'handover' ? 'Karyawan Tujuan' : 'Karyawan Asal');
form.elements.technician_id.required = true;
if (resetItems) { clearItems(); clearFileBox(form.querySelector('[data-filebox]')); }
$('#techBulkSearch').attr('placeholder', action === 'handover' ? 'Scan barang gudang atau cari nama barang' : 'Scan barcode/SN atau cari dari daftar barang teknisi');
$('#techTechnicianStock').toggleClass('d-none', !(isTransfer || isReturn));
if (isTransfer || isReturn) {
if (form.elements.technician_id.value) loadTechnicianHoldings();
else renderTechnicianHoldings();
} else {
technicianHoldings = [];
visibleHoldings = [];
}
}
function clearItems() {
selected = [];
renderItems();
renderTechnicianHoldings();
$('#techBulkSearchResults').addClass('d-none').empty();
}
function itemKey(item) {
return action === 'handover' ? 'S' + item.subject_id : 'A' + item.allocation_id;
}
function addItem(item, silent) {
if (selected.some(row => itemKey(row) === itemKey(item))) {
if (!silent) toast(false, 'Barang tersebut sudah ada dalam daftar. Ubah jumlah pada baris yang tersedia.');
return false;
}
if (selected.length >= 200) {
if (!silent) toast(false, 'Maksimal 200 baris dalam satu dokumen batch.');
return false;
}
item.qty = String(item.tracking_type).toUpperCase() === 'UNIT' ? 1 : Number(item.available_qty);
item.notes = '';
selected.push(item);
renderItems();
$('#techBulkSearch').val('').trigger('focus');
renderTechnicianHoldings('');
$('#techBulkSearchResults').addClass('d-none').empty();
return true;
}
function renderItems() {
const body = $('#techBulkItems tbody');
if (!selected.length) {
body.html('<tr class="tech-bulk-empty"><td colspan="7" class="tech-empty">Belum ada barang yang dipilih.</td></tr>');
} else {
body.html(selected.map((item, index) => {
const unit = String(item.tracking_type).toUpperCase() === 'UNIT';
return '<tr data-index="' + index + '">' +
'<td><strong>' + esc(item.display_name) + '</strong><br><small>' + esc(item.ownership_type === 'company' ? 'Milik Perusahaan' : 'Barang Customer / Non-Persediaan') + '</small></td>' +
'<td>' + esc(item.barcode) + '<br><small>' + esc(item.serial_number || '-') + '</small></td>' +
'<td><span class="tech-status">' + esc(item.tracking_type) + '</span></td>' +
'<td>' + number(item.available_qty) + '</td>' +
'<td>' + (unit ? '<strong>1</strong>' : '<input class="form-control tech-bulk-qty" inputmode="decimal" value="' + esc(item.qty) + '" min="0.0001" max="' + esc(item.available_qty) + '">') + '</td>' +
'<td><input class="form-control tech-bulk-note" value="' + esc(item.notes) + '" placeholder="Opsional"></td>' +
'<td><button type="button" class="btn btn-outline-danger tech-bulk-remove" title="Hapus baris"><i class="bi bi-x-lg"></i></button></td>' +
'</tr>';
}).join(''));
}
$('#techBulkCount').text(selected.length);
$('#techBulkSummary').text(selected.length ? selected.length + ' barang siap diposting dalam satu dokumen' : 'Belum ada barang dipilih');
$(form).find('[type=submit]').prop('disabled', !selected.length);
const needsCompanyWarehouse = action === 'return' && selected.some(item => item.subject_type === 'company_barcode' || item.ownership_type === 'transferred_non_inventory');
$('[data-bulk-field="warehouse"]').find('select').prop('required', needsCompanyWarehouse);
$('#techWarehouseHint').text(needsCompanyWarehouse ? 'Wajib karena terdapat barang milik perusahaan.' : 'Tidak diperlukan jika seluruh barang adalah milik customer.');
}
const holdingStatus = value => ({carried: 'Sedang Dibawa', removed: 'Hasil Pelepasan', returned: 'Dikembalikan'}[value] || value || '-');
function renderTechnicianHoldings(query) {
const panel = $('#techTechnicianStock');
if (!panel.length || action === 'handover') return;
panel.removeClass('d-none');
const normalized = String(query == null ? $('#techBulkSearch').val() : query).trim().toLowerCase();
visibleHoldings = technicianHoldings.filter(item => !normalized || [item.display_name, item.barcode, item.serial_number, item.customer_name, item.usage_status].some(value => String(value || '').toLowerCase().includes(normalized)));
const body = $('#techTechnicianStockBody');
if (!form.elements.technician_id.value) body.html('<tr><td colspan="6" class="tech-empty">Pilih teknisi asal untuk menampilkan barang.</td></tr>');
else if (!technicianHoldings.length) body.html('<tr><td colspan="6" class="tech-empty">Teknisi ini tidak mempunyai barang dalam penguasaan.</td></tr>');
else if (!visibleHoldings.length) body.html('<tr><td colspan="6" class="tech-empty">Tidak ada barang yang cocok dengan pencarian.</td></tr>');
else body.html(visibleHoldings.map(item => {
const checked = selected.some(row => Number(row.allocation_id) === Number(item.allocation_id));
return '<tr class="' + (checked ? 'tech-stock-row-selected' : '') + '"><td><input type="checkbox" class="form-check-input tech-stock-check" data-id="' + Number(item.allocation_id) + '" ' + (checked ? 'checked' : '') + '></td><td><strong>' + esc(item.display_name) + '</strong><br><small>' + esc(item.ownership_type === 'company' ? 'Milik Perusahaan' : 'Barang Customer / Non-Persediaan') + (item.customer_name ? ' · ' + esc(item.customer_name) : '') + '</small></td><td>' + esc(item.barcode) + '<br><small>' + esc(item.serial_number || '-') + '</small></td><td><span class="tech-status">' + esc(holdingStatus(item.usage_status)) + '</span><br><small>' + esc(item.condition_status || '-') + '</small></td><td>' + esc(item.tracking_type) + '</td><td><strong>' + number(item.available_qty) + '</strong></td></tr>';
}).join(''));
const selectedVisible = visibleHoldings.filter(item => selected.some(row => Number(row.allocation_id) === Number(item.allocation_id))).length;
const toggle = document.getElementById('techToggleVisible');
if (toggle) { toggle.checked = visibleHoldings.length > 0 && selectedVisible === visibleHoldings.length; toggle.indeterminate = selectedVisible > 0 && selectedVisible < visibleHoldings.length; }
$('#techTechnicianStockInfo').text((normalized ? visibleHoldings.length + ' dari ' : '') + technicianHoldings.length + ' barang · ' + selected.length + ' dipilih');
}
function loadTechnicianHoldings() {
if (action === 'handover') return;
const technicianId = Number(form.elements.technician_id.value || 0);
technicianHoldings = [];
visibleHoldings = [];
$('#techTechnicianStock').removeClass('d-none');
$('#techTechnicianStockBody').html('<tr><td colspan="6" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat barang teknisi...</td></tr>');
$('#techTechnicianStockInfo').text('Memuat daftar...');
if (!technicianId) return renderTechnicianHoldings();
if (holdingsRequest && holdingsRequest.readyState !== 4) holdingsRequest.abort();
holdingsRequest = $.getJSON(base + 'teknisi/equipment_technician_items', {technician_id: technicianId})
.done(response => {
if (Number(form.elements.technician_id.value || 0) !== technicianId) return;
technicianHoldings = response.data || [];
renderTechnicianHoldings();
const initialQuery = $('#techBulkSearch').val().trim();
if (initialQuery) performSearch();
})
.fail(xhr => {
if (xhr.statusText === 'abort') return;
$('#techTechnicianStockBody').html('<tr><td colspan="6" class="tech-empty text-danger">Daftar barang teknisi gagal dimuat.</td></tr>');
toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Daftar barang teknisi gagal dimuat.');
});
}
function renderSearch(rows) {
const target = $('#techBulkSearchResults');
if (!rows.length) {
target.removeClass('d-none').html('<div class="tech-empty py-3">Barang tidak ditemukan atau tidak tersedia pada posisi yang dipilih.</div>');
return;
}
target.removeClass('d-none').html(rows.map((item, index) => '<button type="button" class="tech-search-option" data-index="' + index + '"><span><strong>' + esc(item.display_name) + '</strong><small>' + esc(item.barcode) + (item.serial_number ? ' · SN ' + esc(item.serial_number) : '') + (item.technician_name ? ' · ' + esc(item.technician_name) : '') + '</small></span><span><b>' + number(item.available_qty) + '</b><small>' + esc(item.tracking_type) + '</small></span></button>').join(''));
}
function performSearch() {
const query = $('#techBulkSearch').val().trim();
const technicianId = Number(form.elements.technician_id.value || 0);
if ((action === 'transfer' || action === 'return') && !technicianId) return toast(false, 'Pilih teknisi asal terlebih dahulu.');
if (action === 'transfer' || action === 'return') {
if (!query) { renderTechnicianHoldings(''); return; }
const exact = technicianHoldings.filter(item => [item.barcode, item.serial_number].some(value => String(value || '').toLowerCase() === query.toLowerCase()));
if (exact.length === 1) addItem(exact[0]);
else renderTechnicianHoldings(query);
return;
}
if (!query) return toast(false, 'Scan barcode atau isi kata pencarian.');
const button = $('#techBulkSearchButton').prop('disabled', true);
$.getJSON(base + 'teknisi/equipment_search', {q: query, mode: action, technician_id: technicianId})
.done(response => {
searchResults = response.data || [];
const exact = searchResults.filter(item => [item.barcode, item.serial_number].some(value => String(value || '').toLowerCase() === query.toLowerCase()));
if (exact.length === 1) addItem(exact[0]);
else if (searchResults.length === 1) addItem(searchResults[0]);
else renderSearch(searchResults);
})
.fail(xhr => toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Pencarian barang gagal.'))
.always(() => button.prop('disabled', false));
}
$('.tech-bulk-action').on('click', function () {
if (this.dataset.action === action) return;
setAction(this.dataset.action, true);
});
$('#techBulkSearchButton').on('click', performSearch);
$('#techBulkSearch').on('keydown', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
performSearch();
}
});
$('#techBulkSearch').on('input', function () {
if (action === 'transfer' || action === 'return') renderTechnicianHoldings(this.value);
});
$(document).on('click', '.tech-search-option', function () { addItem(searchResults[Number(this.dataset.index)]); });
$(document).on('change', '.tech-stock-check', function () {
const allocationId = Number(this.dataset.id);
const item = technicianHoldings.find(row => Number(row.allocation_id) === allocationId);
if (!item) return;
if (this.checked) { if (!addItem(item, true)) { this.checked = false; toast(false, 'Maksimal 200 baris dalam satu dokumen batch.'); renderTechnicianHoldings(); } }
else { selected = selected.filter(row => Number(row.allocation_id) !== allocationId); renderItems(); renderTechnicianHoldings(); }
});
$('#techSelectVisible').on('click', function () {
let added = 0;
for (const item of visibleHoldings) {
if (selected.length >= 200) break;
if (!selected.some(row => Number(row.allocation_id) === Number(item.allocation_id))) { item.qty = String(item.tracking_type).toUpperCase() === 'UNIT' ? 1 : Number(item.available_qty); item.notes = ''; selected.push(item); added++; }
}
renderItems();renderTechnicianHoldings();
if (visibleHoldings.length > 200 || selected.length >= 200 && visibleHoldings.some(item => !selected.some(row => Number(row.allocation_id) === Number(item.allocation_id)))) toast(false, 'Maksimal 200 baris per dokumen. Sisa barang dapat diproses pada batch berikutnya.');
else if (added) toast(true, added + ' barang ditambahkan.');
});
$('#techToggleVisible').on('change', function () { if (this.checked) $('#techSelectVisible').trigger('click'); else { const visibleIds = new Set(visibleHoldings.map(item => Number(item.allocation_id))); selected = selected.filter(item => !visibleIds.has(Number(item.allocation_id))); renderItems();renderTechnicianHoldings(); } });
$('#techClearSelection').on('click', function () { selected = [];renderItems();renderTechnicianHoldings(); });
$(document).on('click', '.tech-bulk-remove', function () {
selected.splice(Number($(this).closest('tr').data('index')), 1);
renderItems();
renderTechnicianHoldings();
});
$(document).on('input', '.tech-bulk-qty', function () { selected[Number($(this).closest('tr').data('index'))].qty = this.value; });
$(document).on('input', '.tech-bulk-note', function () { selected[Number($(this).closest('tr').data('index'))].notes = this.value; });
$(form.elements.technician_id).on('change', function () {
if (action !== 'transfer' && action !== 'return') return;
const hadSelection = selected.length > 0;
selected = [];renderItems();
$('#techBulkSearch').val('');
loadTechnicianHoldings();
if (hadSelection) toast(true, 'Pilihan barang dikosongkan karena teknisi asal berubah.');
});
form.addEventListener('submit', async function (event) {
event.preventDefault();
if (!form.reportValidity()) return;
if (!selected.length) return toast(false, 'Tambahkan minimal satu barang.');
for (const item of selected) {
const amount = Number(item.qty);
if (!(amount > 0) || amount > Number(item.available_qty) + 0.0001) return toast(false, 'Jumlah untuk ' + item.display_name + ' tidak valid.');
}
if (action === 'return' && !form.querySelector('input[type=file]').files.length) return toast(false, 'Bukti pengembalian wajib dilampirkan satu kali untuk transaksi batch.');
const confirmed = !window.Swal || (await Swal.fire({icon: 'question', title: 'Posting ' + selected.length + ' barang?', text: 'Seluruh barang akan diproses dalam satu dokumen.', showCancelButton: true, confirmButtonText: 'Ya, posting', cancelButtonText: 'Periksa lagi', allowOutsideClick: false})).isConfirmed;
if (!confirmed) return;
const submit = $(form).find('[type=submit]').prop('disabled', true);
const payload = new FormData(form);
payload.set('idempotency_key', form.elements.idempotency_key.value || key());
payload.set('items_json', JSON.stringify(selected.map(item => ({subject_id: item.subject_id || null, allocation_id: item.allocation_id || null, qty: Number(item.qty), notes: item.notes || ''}))));
$.ajax({url: base + 'teknisi/process_equipment_batch', method: 'POST', data: payload, processData: false, contentType: false, dataType: 'json'})
.done(response => {
if (!response.status) return toast(false, response.message || 'Transaksi batch gagal.');
toast(true, response.message);
clearItems();
form.elements.idempotency_key.value = key();
clearFileBox(form.querySelector('[data-filebox]'));
if (action === 'transfer' || action === 'return') loadTechnicianHoldings();
})
.fail(xhr => toast(false, (xhr.responseJSON && xhr.responseJSON.message) || 'Transaksi batch gagal diproses.'))
.always(() => submit.prop('disabled', !selected.length));
});
function setupFileBox(box) {
if (!box) return;
const input = box.querySelector('input[type=file]');
fileSelections.set(box, []);
input.addEventListener('change', () => {
const files = (fileSelections.get(box) || []).concat(Array.from(input.files)).slice(0, 10);
fileSelections.set(box, files);
syncFiles(box);
});
}
function syncFiles(box) {
if (!box) return;
const input = box.querySelector('input[type=file]');
const add = box.querySelector('.tech-add-file');
const files = fileSelections.get(box) || [];
box.querySelectorAll('.tech-thumb').forEach(element => element.remove());
files.forEach((file, index) => {
const thumb = document.createElement('div');
thumb.className = 'tech-thumb';
if (file.type === 'application/pdf') thumb.innerHTML = '<div class="tech-pdf"><i class="bi bi-file-pdf fs-2 text-danger"></i></div>';
else { const image = document.createElement('img'); image.src = URL.createObjectURL(file); image.alt = file.name; thumb.appendChild(image); }
const remove = document.createElement('button');
remove.type = 'button'; remove.innerHTML = '&times;'; remove.title = 'Hapus';
remove.addEventListener('click', event => { event.stopPropagation(); fileSelections.set(box, (fileSelections.get(box) || []).filter((_, itemIndex) => itemIndex !== index)); syncFiles(box); });
thumb.appendChild(remove);
thumb.addEventListener('click', event => { if (event.target === remove) return; preview(URL.createObjectURL(file), file.type); });
box.insertBefore(thumb, add);
});
const transfer = new DataTransfer();
files.forEach(file => transfer.items.add(file));
input.files = transfer.files;
}
function clearFileBox(box) { if (!box) return; fileSelections.set(box, []); syncFiles(box); }
function preview(url, mime) {
const body = document.querySelector('#techBulkPreviewModal [data-preview-body]');
body.innerHTML = mime === 'application/pdf' ? '<iframe title="Preview PDF" src="' + esc(url) + '" style="width:100%;height:100%;border:0"></iframe>' : '<div class="h-100 d-grid align-items-center text-center"><img src="' + esc(url) + '" alt="Preview" style="max-width:100%;max-height:100%;margin:auto;object-fit:contain"></div>';
modal('techBulkPreviewModal', true);
}
document.querySelectorAll('[data-filebox]').forEach(setupFileBox);
const firstAction = document.querySelector('.tech-bulk-action');
const initial = new URLSearchParams(location.search);
const requestedAction = initial.get('action');
const requestedButton = requestedAction ? document.querySelector('.tech-bulk-action[data-action="' + requestedAction + '"]') : null;
if (requestedButton || firstAction) setAction((requestedButton || firstAction).dataset.action, false);
if (initial.get('technician_id')) form.elements.technician_id.value = initial.get('technician_id');
else if (form.elements.technician_id.options.length === 2) form.elements.technician_id.selectedIndex = 1;
initEmployeeSearch();
$(form).find('select[name="technician_id"],select[name="target_technician_id"]').trigger('change.select2');
if (initial.get('scan')) $('#techBulkSearch').val(initial.get('scan'));
if ((action === 'transfer' || action === 'return') && form.elements.technician_id.value) loadTechnicianHoldings();
else if (initial.get('scan')) performSearch();
renderItems();
})(jQuery);
+78
View File
@@ -0,0 +1,78 @@
(function($){
'use strict';
const cfg=window.TECH_EQUIPMENT||{};const base=cfg.base||'/';const perms=cfg.permissions||{};let scanned=null,replacementAllocations=[],technicianTable=null,documentTable=null,customerNotesTable=null;
const esc=v=>$('<div>').text(v==null?'':String(v)).html();
const qty=v=>{const n=Number(v||0);return Number.isInteger(n)?n.toLocaleString('id-ID'):n.toLocaleString('id-ID',{maximumFractionDigits:4})};
const labels={company:'Milik Perusahaan',customer:'Milik Customer',transferred_non_inventory:'Diserahkan Non-Persediaan',unknown:'Belum Diketahui',warehouse:'Gudang',technician:'Teknisi',customer_custody:'Customer',quarantine:'Menunggu Pemeriksaan',service_center:'Pusat Perbaikan',carried:'Dibawa',installed:'Dipasang',removed:'Dilepas',returned:'Dikembalikan',good:'Baik',inspection_required:'Perlu Diperiksa',repair_required:'Perlu Perbaikan',under_repair:'Dalam Perbaikan',damaged:'Rusak',lost:'Hilang',scrapped:'Dihapuskan'};
const actionLabels={opening:'Saldo Awal Migrasi',handover:'Serahkan ke Teknisi',install:'Pasang di Customer',remove:'Lepas ke Teknisi',transfer:'Transfer Teknisi',return:'Kembali ke Gudang',return_customer:'Kembali ke Pemilik',inspect:'Periksa Barang',inspection:'Pemeriksaan',replacement:'Penggantian',correction:'Koreksi / Reversal'};
const toast=(ok,msg)=>{if(window.Swal)Swal.fire({toast:true,position:'top-end',icon:ok?'success':'error',title:msg,showConfirmButton:false,timer:ok?2600:4500,timerProgressBar:true});else alert(msg)};
const confirmPost=async title=>{if(!window.Swal)return confirm(title);const r=await Swal.fire({icon:'question',title:title,text:'Pastikan data, jumlah, dan dokumen sudah benar.',showCancelButton:true,confirmButtonText:'Ya, posting',cancelButtonText:'Periksa lagi',allowOutsideClick:false});return r.isConfirmed};
function modal(id,show=true){const el=document.getElementById(id);if(!el)return;if(window.bootstrap&&bootstrap.Modal){const m=bootstrap.Modal.getOrCreateInstance(el,{backdrop:'static',keyboard:false});show?m.show():m.hide()}else $(el).modal(show?'show':'hide')}
function randomKey(){return 'TEQ-'+Date.now()+'-'+Math.random().toString(36).slice(2)+cryptoPart()}
function cryptoPart(){try{const a=new Uint32Array(1);crypto.getRandomValues(a);return '-'+a[0]}catch(e){return ''}}
function positionText(a){if(a.custodian_type==='technician')return 'Teknisi: '+(a.technician_name||'-');if(a.custodian_type==='customer')return 'Customer: '+(a.customer_name||'-');if(a.custodian_type==='quarantine')return 'Karantina: '+(a.warehouse_name||'-');return labels[a.custodian_type]||a.custodian_type}
function can(action){const map={handover:'handover',install:'install',remove:'remove',transfer:'transfer',return:'return',return_customer:'return',inspect:'inspect',replacement:'install'};return !!perms[map[action]]}
const tableLanguage={search:'Cari:',lengthMenu:'Tampilkan _MENU_ data',info:'Menampilkan _START_ - _END_ dari _TOTAL_ data',infoEmpty:'Belum ada data',zeroRecords:'Data tidak ditemukan',paginate:{next:'&raquo;',previous:'&laquo;'}};
function createSearchTable(selector,order,hasActionColumn=true){if(!$.fn.DataTable||!document.querySelector(selector))return null;const body=document.querySelector(selector+' tbody'),hasRows=body&&body.querySelector('tr')&&!body.querySelector('td[colspan]');if(!hasRows)return null;if($.fn.DataTable.isDataTable(selector))$(selector).DataTable().destroy();return $(selector).DataTable({pageLength:10,order:order||[],responsive:true,autoWidth:false,language:tableLanguage,columnDefs:hasActionColumn?[{targets:-1,orderable:false,searchable:false}]:[]})}
function initEmployeeSearch(scope){if(!$.fn.select2)return;$(scope||document).find('select[name="technician_id"],select[name="target_technician_id"],select[name="user_id"]').each(function(){const select=$(this);if(select.hasClass('select2-hidden-accessible'))return;const parent=select.closest('.modal');select.select2({width:'100%',placeholder:select.find('option:first').text()||'Cari karyawan',allowClear:true,dropdownParent:parent.length?parent:$(document.body)})})}
function renderScan(item){
let allocations=(item.allocations||[]).map(a=>{let acts=[];if(['quarantine','service_center'].includes(a.custodian_type)&&['inspection_required','repair_required','under_repair','damaged'].includes(a.condition_status))acts=['inspect'];else if(a.custodian_type==='technician')acts=['install','transfer','return'].concat(item.subject_type==='customer_equipment'?['return_customer']:[]);else if(a.custodian_type==='customer'&&a.usage_status==='installed')acts=['remove','replacement'];const buttons=acts.filter(can).map(x=>x==='replacement'?'<a class="btn btn-sm btn-outline-primary" href="'+base+'teknisi/replacement_workflow"><i class="bi bi-arrow-repeat"></i> '+actionLabels[x]+'</a>':(['transfer','return'].includes(x)?'<a class="btn btn-sm btn-outline-primary" href="'+base+'teknisi/bulk_workflow?action='+x+'&technician_id='+Number(a.custodian_id||0)+'&scan='+encodeURIComponent(item.barcode||a.barcode_snapshot)+'"><i class="bi bi-box-arrow-right"></i> '+actionLabels[x]+' (Batch)</a>':'<button class="btn btn-sm btn-outline-primary tech-action" data-action="'+x+'" data-allocation="'+a.id+'">'+actionLabels[x]+'</button>')).join('');return '<tr><td>'+esc(positionText(a))+'</td><td><span class="tech-status">'+esc(labels[a.usage_status]||a.usage_status)+'</span></td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td><td>'+qty(a.qty)+'</td><td><div class="tech-actions mt-0">'+buttons+'</div></td></tr>'}).join('');
let handover=item.subject_type==='company_barcode'&&Number(item.warehouse_available)>0&&can('handover')?'<a class="btn btn-primary" href="'+base+'teknisi/bulk_workflow?action=handover&scan='+encodeURIComponent(item.barcode)+'"><i class="bi bi-person-plus"></i> Tambahkan ke Penyerahan Batch</a>':'';
$('#techScanResult').removeClass('tech-empty').html('<div class="tech-identity"><div class="tech-kv"><small>Barang</small><strong>'+esc(item.display_name)+'</strong><br><span class="'+(item.ownership_type==='company'?'tech-owner-company':'tech-owner-customer')+'">'+esc(labels[item.ownership_type]||item.ownership_type)+'</span></div><div class="tech-kv"><small>Barcode</small>'+esc(item.barcode||'-')+'</div><div class="tech-kv"><small>Serial</small>'+esc(item.serial_number||'-')+'</div><div class="tech-kv"><small>Jenis</small>'+esc(item.tracking_type||'-')+'</div><div class="tech-kv"><small>Tersedia di Gudang</small>'+qty(item.warehouse_available||0)+'</div></div><div class="tech-actions">'+handover+'</div><div class="tech-table-wrap mt-3"><table class="table tech-table"><thead><tr><th>Posisi</th><th>Penggunaan</th><th>Kondisi</th><th>Qty</th><th>Tindakan Valid</th></tr></thead><tbody>'+(allocations||'<tr><td colspan="5" class="tech-empty">Tidak ada saldo di luar gudang.</td></tr>')+'</tbody></table></div>');
}
function loadScanned(code){const btn=$('#techScanForm button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_scan',{code:code}).done(r=>{if(!r.status)return toast(false,r.message||'Barcode tidak dapat diperiksa.');scanned=r.result||r.data&&r.data.result;if(!scanned)return toast(false,'Respons barcode tidak lengkap.');renderScan(scanned)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Barcode tidak dapat diperiksa.')).always(()=>btn.prop('disabled',false))}
function renderLookup(rows){$('#techScanResult').removeClass('tech-empty').html('<div class="tech-card-head mb-2"><div><strong>Hasil pencarian</strong><p>Pilih barcode yang akan diperiksa.</p></div><span class="tech-status">'+rows.length+' hasil</span></div><div class="tech-search-results">'+rows.map((row,index)=>'<button type="button" class="tech-search-option tech-lookup-select" data-code="'+esc(row.lookup_code)+'"><span><strong>'+esc(row.display_name)+'</strong><small>'+esc(row.barcode)+(row.serial_number?' · SN '+esc(row.serial_number):'')+'</small></span><span><b>'+qty(row.available_qty)+'</b><small>'+esc(row.tracking_type)+'</small></span></button>').join('')+'</div>')}
$('#techScanForm').on('submit',function(e){e.preventDefault();const query=$('#techScanCode').val().trim();if(!query)return toast(false,'Scan barcode atau isi nama barang.');const btn=$(this).find('button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_search',{q:query,mode:'all'}).done(r=>{const rows=r.data||[];if(!rows.length)return toast(false,'Barang tidak ditemukan.');const exact=rows.filter(row=>[row.barcode,row.serial_number].some(value=>String(value||'').toLowerCase()===query.toLowerCase()));if(exact.length===1)loadScanned(exact[0].lookup_code);else if(rows.length===1)loadScanned(rows[0].lookup_code);else renderLookup(rows)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Pencarian barang gagal.')).always(()=>btn.prop('disabled',false))});
$(document).on('click','.tech-lookup-select',function(){$('#techScanCode').val(this.dataset.code);loadScanned(this.dataset.code)});
function allocation(id){return scanned&&scanned.allocations?(scanned.allocations.find(x=>String(x.id)===String(id))||null):null}
function openAction(action,id){const a=id?allocation(id):null;const form=$('#techActionForm')[0];form.reset();form.elements.action.value=action;form.elements.subject_id.value=scanned?scanned.subject_id:'';form.elements.allocation_id.value=id||'';form.elements.idempotency_key.value=randomKey();form.elements.date.value=new Date().toISOString().slice(0,10);$('#techActionForm .modal-title').text(actionLabels[action]||'Catat Pergerakan');$('#techActionSubject').html('<strong>'+esc(scanned?scanned.display_name:(a?a.display_name:'Barang'))+'</strong><br><small>'+esc(scanned?(scanned.barcode||''):(a?a.barcode_snapshot:''))+(a?' · '+esc(positionText(a))+' · Saldo '+qty(a.qty):'')+'</small>');
const needs={qty:true,technician:['handover','remove'].includes(action),target_technician:action==='transfer',customer:['install','return_customer'].includes(action),warehouse:action==='return',condition:action==='remove',inspection:action==='inspect',location:action==='install',reason:['remove','return','transfer'].includes(action)};Object.keys(needs).forEach(k=>{$('[data-field="'+k+'"]').toggleClass('d-none',!needs[k]).find('input,select').prop('required',!!needs[k])});
if(action==='inspect'&&a){form.elements.allocation_id.value=a.id}if((scanned&&scanned.tracking_type==='UNIT')||(a&&a.tracking_type==='UNIT')){$('[data-field="qty"]').addClass('d-none');form.elements.qty.value=1}else form.elements.qty.value=a?a.qty:(scanned?scanned.warehouse_available:'');clearFileBox($('#techActionForm [data-filebox]')[0]);modal('techActionModal',true)}
$(document).on('click','.tech-action',function(){openAction(this.dataset.action,this.dataset.allocation)});
$('#techActionForm').on('submit',async function(e){e.preventDefault();const form=this,action=form.elements.action.value;modal('techActionModal',false);await new Promise(resolve=>setTimeout(resolve,180));if(!(await confirmPost(actionLabels[action]||'Posting pergerakan?'))){modal('techActionModal',true);return}const btn=$(form).find('[type=submit]').prop('disabled',true);const url=action==='inspect'?'teknisi/process_inspection':'teknisi/process_equipment';$.ajax({url:base+url,method:'POST',data:new FormData(form),processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status){toast(false,r.message);modal('techActionModal',true);return}toast(true,r.message);if($('#techScanCode').length)$('#techScanForm').trigger('submit');loadInspection();loadDocuments()}).fail(x=>{toast(false,(x.responseJSON&&x.responseJSON.message)||'Transaksi gagal diproses.');modal('techActionModal',true)}).always(()=>btn.prop('disabled',false))});
function loadDocuments(){const table=$('#techDocumentsTable tbody');if(!table.length)return;if(documentTable){documentTable.destroy();documentTable=null}$.getJSON(base+'teknisi/equipment_documents').done(r=>{const rows=(r.data||[]).slice(0,100);table.html(rows.length?rows.map(d=>'<tr><td><strong>'+esc(d.document_no)+'</strong><br><small>'+esc(d.status)+'</small></td><td>'+esc(d.document_date)+'</td><td>'+esc(actionLabels[d.document_type]||d.document_type)+'</td><td>'+esc(d.technician_name||'-')+'<br><small>'+esc(d.customer_name||'-')+'</small></td><td>'+qty(d.line_count)+'</td><td><div class="tech-doc-actions"><button class="btn btn-outline-primary btn-sm tech-doc-detail" data-id="'+d.id+'"><i class="bi bi-eye"></i> Detail</button>'+(perms.export?'<button class="btn btn-outline-danger btn-sm tech-doc-pdf" data-id="'+d.id+'"><i class="bi bi-file-pdf"></i> PDF</button>':'')+'</div></td></tr>').join(''):'<tr><td colspan="6" class="tech-empty">Belum ada dokumen operasional.</td></tr>');if(rows.length)documentTable=createSearchTable('#techDocumentsTable',[[1,'desc']])}).fail(()=>table.html('<tr><td colspan="6" class="tech-empty text-danger">Riwayat gagal dimuat.</td></tr>'))}
$(document).on('click','.tech-doc-detail',function(){const id=this.dataset.id;$.getJSON(base+'teknisi/equipment_document/'+id).done(r=>{const d=r.result;let lines=(d.lines||[]).map(l=>'<tr><td>'+esc(l.display_name)+'</td><td>'+esc(l.barcode_snapshot)+(l.serial_number?'<br><small>SN '+esc(l.serial_number)+'</small>':'')+'</td><td>'+esc(labels[l.ownership_type]||l.ownership_type)+'</td><td>'+esc(labels[l.from_custodian_type]||l.from_custodian_type)+' → '+esc(labels[l.to_custodian_type]||l.to_custodian_type)+'</td><td>'+qty(l.qty)+'</td><td>'+esc(l.notes||'-')+'</td></tr>').join('');let files=(perms.attachment?d.attachments||[]:[]).map(f=>'<button class="btn btn-outline-secondary btn-sm tech-attachment" data-url="'+base+'accountingattachments/preview/'+f.id+'" data-mime="'+esc(f.mime_type)+'"><i class="bi '+(f.mime_type==='application/pdf'?'bi-file-pdf':'bi-image')+'"></i> '+esc(f.original_name)+'</button>').join(' ');let correction=perms.correct&&d.status==='posted'&&d.document_type!=='correction'&&d.document_type!=='opening'?'<button class="btn btn-outline-warning tech-correct-doc mt-3" data-id="'+d.id+'" data-no="'+esc(d.document_no)+'"><i class="bi bi-arrow-counterclockwise"></i> Koreksi / Reversal</button>':'';$('#techDocumentBody').html('<div class="tech-modal-summary"><div><small>Nomor / Tanggal</small><strong>'+esc(d.document_no)+'</strong><br>'+esc(d.document_date)+'</div><div><small>Proses / Teknisi</small>'+esc(actionLabels[d.document_type]||d.document_type)+'<br>'+esc(d.technician_name||'-')+'</div><div><small>Customer / Gudang</small>'+esc(d.customer_name||'-')+'<br>'+esc(d.warehouse_name||'-')+'</div></div><p class="mt-3"><strong>Keterangan:</strong> '+esc(d.notes||'-')+'</p><div class="tech-table-wrap"><table class="table tech-table"><thead><tr><th>Barang</th><th>Barcode</th><th>Pemilik</th><th>Perpindahan</th><th>Qty</th><th>Keterangan</th></tr></thead><tbody>'+lines+'</tbody></table></div><div class="mt-3"><strong>Lampiran</strong><div class="tech-actions">'+(files||'<span class="text-muted">Tidak ada lampiran atau akses tidak diberikan.</span>')+'</div></div>'+correction);modal('techDocumentModal',true)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Detail tidak dapat dibuka.'))});
$(document).on('click','.tech-correct-doc',async function(){const id=this.dataset.id,no=this.dataset.no;modal('techDocumentModal',false);if(!window.Swal)return;const r=await Swal.fire({icon:'warning',title:'Koreksi '+no,html:'<label class="w-100 text-start">Tanggal koreksi<input id="techCorrectionDate" type="date" class="swal2-input mx-0 w-100" value="'+new Date().toISOString().slice(0,10)+'"></label><label class="w-100 text-start">Alasan wajib<textarea id="techCorrectionReason" class="swal2-textarea mx-0 w-100" placeholder="Jelaskan alasan koreksi"></textarea></label>',showCancelButton:true,confirmButtonText:'Buat Reversal',cancelButtonText:'Batal',allowOutsideClick:false,preConfirm:()=>{const date=document.getElementById('techCorrectionDate').value,reason=document.getElementById('techCorrectionReason').value.trim();if(!date||!reason){Swal.showValidationMessage('Tanggal dan alasan koreksi wajib diisi.');return false}return{date,reason}}});if(!r.isConfirmed)return;$.post(base+'teknisi/process_correction',{document_id:id,date:r.value.date,reason:r.value.reason,idempotency_key:randomKey()},res=>{toast(!!res.status,res.message);if(res.status)loadDocuments()},'json').fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Koreksi gagal diproses.'))});
$(document).on('click','.tech-doc-pdf',function(){preview(base+'teknisi/equipment_document_pdf/'+this.dataset.id,'application/pdf')});
$(document).on('click','.tech-attachment',function(){preview(this.dataset.url,this.dataset.mime)});
function preview(url,mime){$('#techPreviewBody').html(mime==='application/pdf'?'<iframe title="Preview PDF" src="'+esc(url)+'" style="width:100%;height:100%;border:0"></iframe>':'<div class="h-100 d-grid align-items-center text-center"><img src="'+esc(url)+'" alt="Preview" style="max-width:100%;max-height:100%;margin:auto;object-fit:contain"></div>');modal('techPreviewModal',true)}
function replacementCandidates(kind,query){const form=document.getElementById('techReplacementForm');if(!form)return[];const tech=Number(form.elements.technician_id.value||0),customer=Number(form.elements.customer_id.value||0),q=String(query||'').trim().toLowerCase();return replacementAllocations.filter(a=>{const eligible=kind==='carried'?a.custodian_type==='technician'&&Number(a.custodian_id)===tech&&['carried','removed','returned'].includes(a.usage_status):a.custodian_type==='customer'&&Number(a.customer_id)===customer&&a.usage_status==='installed';return eligible&&(!q||[a.display_name,a.barcode_snapshot,a.serial_number,a.external_serial].some(v=>String(v||'').toLowerCase().includes(q)))})}
function renderReplacementSelect(kind,query){const form=document.getElementById('techReplacementForm');if(!form)return;const select=form.querySelector('.tech-allocation-select[data-kind="'+kind+'"]'),current=select.value,rows=replacementCandidates(kind,query),needs=kind==='carried'?!form.elements.technician_id.value:!form.elements.customer_id.value;select.innerHTML='<option value="">'+(needs?(kind==='carried'?'Pilih teknisi terlebih dahulu':'Pilih customer terlebih dahulu'):(rows.length?'Pilih dari '+rows.length+' barang':'Tidak ada barang yang sesuai'))+'</option>'+rows.map(a=>'<option value="'+a.id+'">'+esc(a.display_name)+' · '+esc(a.barcode_snapshot)+(a.serial_number||a.external_serial?' · SN '+esc(a.serial_number||a.external_serial):'')+' · '+qty(a.qty)+' · '+esc(a.ownership_type==='company'?'Milik Perusahaan':'Milik Customer / Non-Persediaan')+'</option>').join('');if(rows.some(a=>String(a.id)===String(current)))select.value=current;else if(current)$(select).trigger('change');const hint=document.getElementById(kind==='carried'?'techOutgoingHint':'techIncomingHint');if(hint)hint.textContent=needs?(kind==='carried'?'Pilih teknisi terlebih dahulu.':'Pilih customer terlebih dahulu.'):(rows.length+' barang ditemukan'+(query?' untuk pencarian ini.':'.'))}
function loadAllocs(){if(!$('.tech-allocation-select').length)return;$.getJSON(base+'teknisi/equipment_allocations').done(r=>{replacementAllocations=r.data||[];renderReplacementSelect('carried','');renderReplacementSelect('installed','')}).fail(()=>toast(false,'Daftar posisi barang gagal dimuat.'))}
function selectReplacementBySearch(kind){const input=document.getElementById(kind==='carried'?'techOutgoingSearch':'techIncomingSearch'),query=input.value.trim();if(!query)return renderReplacementSelect(kind,'');const rows=replacementCandidates(kind,query),exact=rows.filter(a=>[a.barcode_snapshot,a.serial_number,a.external_serial].some(v=>String(v||'').toLowerCase()===query.toLowerCase()));renderReplacementSelect(kind,exact.length===1?'':query);const select=document.querySelector('.tech-allocation-select[data-kind="'+kind+'"]');if(exact.length===1){select.value=exact[0].id;$(select).trigger('change');input.value='';renderReplacementSelect(kind,'')}else if(rows.length===1){select.value=rows[0].id;$(select).trigger('change')}else if(!rows.length)toast(false,'Barang tidak ditemukan pada teknisi/customer yang dipilih.');else{select.focus();toast(true,rows.length+' barang sesuai pencarian. Pilih salah satu dari daftar.')}}
$(document).on('click','.tech-replacement-search',function(){selectReplacementBySearch(this.dataset.kind)});
$('#techOutgoingSearch,#techIncomingSearch').on('keydown',function(e){if(e.key==='Enter'){e.preventDefault();selectReplacementBySearch(this.id==='techOutgoingSearch'?'carried':'installed')}}).on('input',function(){renderReplacementSelect(this.id==='techOutgoingSearch'?'carried':'installed',this.value)});
$('#techReplacementForm [name=technician_id]').on('change',function(){$('#techOutgoingSearch').val('');renderReplacementSelect('carried','')});
$('#techReplacementForm [name=customer_id]').on('change',function(){$('#techIncomingSearch').val('');renderReplacementSelect('installed','')});
$(document).on('change','.tech-allocation-select',function(){const row=replacementAllocations.find(a=>String(a.id)===String(this.value)),kind=this.dataset.kind,info=document.getElementById(kind==='carried'?'techOutgoingInfo':'techIncomingInfo'),amount=document.querySelector('#techReplacementForm [name="'+(kind==='carried'?'outgoing_qty':'incoming_qty')+'"]');if(!row){if(info)info.textContent=kind==='carried'?'Belum ada barang dipilih.':'Bisa milik perusahaan maupun milik customer.';if(amount){amount.readOnly=false;if(kind==='carried')amount.value='';}return}if(info)info.innerHTML='<strong>'+esc(row.ownership_type==='company'?'Milik Perusahaan':'Milik Customer / Non-Persediaan')+'</strong> · '+esc(row.tracking_type)+' · Saldo '+qty(row.qty)+' · '+esc(positionText(row));if(amount){amount.value=String(row.tracking_type).toUpperCase()==='UNIT'?1:Number(row.qty);amount.readOnly=String(row.tracking_type).toUpperCase()==='UNIT';amount.max=row.qty}});
$('#techNewOldItem').on('change',function(){const manual=this.checked;$('#techOldItemFields').toggleClass('d-none',!manual);$('#techReplacementForm [name=incoming_allocation_id]').prop('disabled',manual).prop('required',!manual);$('#techIncomingSearch').prop('disabled',manual);$('#techOldItemFields [name=old_equipment_name],#techOldItemFields [name=old_barcode]').prop('required',manual)});
$('#techReplacementForm [name=incoming_allocation_id]').prop('required',true);
$('#techReplacementForm').on('submit',async function(e){e.preventDefault();if(!(await confirmPost('Posting pemasangan dan penggantian?')))return;const btn=$(this).find('[type=submit]').prop('disabled',true),fd=new FormData(this);fd.set('idempotency_key',randomKey());$.ajax({url:base+'teknisi/process_replacement',method:'POST',data:fd,processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status)return toast(false,r.message);toast(true,r.message);this.reset();$('#techNewOldItem').trigger('change');$('#techOutgoingSearch,#techIncomingSearch').val('');$('#techOutgoingInfo').text('Belum ada barang dipilih.');$('#techIncomingInfo').text('Bisa milik perusahaan maupun milik customer.');$('#techReplacementForm [name=outgoing_qty]').val('').prop('readonly',false).removeAttr('max');$('#techReplacementForm [name=incoming_qty]').val('1').prop('readonly',false).removeAttr('max');clearFileBox($(this).find('[data-filebox]')[0]);loadAllocs()}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Penggantian gagal diproses.')).always(()=>btn.prop('disabled',false))});
function loadInspection(){const body=$('#techInspectionTable tbody');if(!body.length)return;const query=$('#techInspectionFilter').serialize();$.getJSON(base+'teknisi/inspection_data'+(query?'?'+query:'')).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>{const waiting=Number(a.requires_return)===1;let position=waiting?'Masih dibawa teknisi':(a.custodian_type==='customer'?'Gudang customer: '+(a.customer_name||'-'):(a.custodian_type==='warehouse'?'Gudang perusahaan: '+(a.warehouse_name||'-'):(a.warehouse_name||'Karantina gudang')));let action='Menunggu dikembalikan';if(waiting&&perms.return)action='<a class="btn btn-warning btn-sm" href="'+base+'teknisi/bulk_workflow?action=return&technician_id='+encodeURIComponent(a.technician_id||'')+'&scan='+encodeURIComponent(a.barcode_snapshot||'')+'"><i class="bi bi-box-arrow-in-down"></i> Kembalikan</a>';else if(!waiting&&perms.inspect)action='<button class="btn btn-primary btn-sm tech-inspect-row" data-row="'+encodeURIComponent(JSON.stringify(a))+'">Periksa</button>';return'<tr><td><strong>'+esc(a.display_name)+'</strong><br><small>'+esc(a.document_date||'-')+'</small></td><td>'+esc(a.barcode_snapshot)+(a.serial_number?'<br><small>'+esc(a.serial_number)+'</small>':'')+'</td><td>'+esc(labels[a.ownership_type]||a.ownership_type)+'</td><td>'+esc(position)+'</td><td>'+esc(a.technician_name||'-')+'<br><small>'+esc(a.customer_name||'-')+'</small></td><td>'+qty(a.qty)+'</td><td>'+action+'</td></tr>'}).join(''):'<tr><td colspan="7" class="tech-empty">Tidak ada barang yang perlu dikembalikan atau diperiksa.</td></tr>')}).fail(()=>body.html('<tr><td colspan="7" class="tech-empty text-danger">Antrean gagal dimuat.</td></tr>'))}
$('#techInspectionFilter').on('submit',function(e){e.preventDefault();loadInspection()});
$(document).on('click','.tech-inspect-row',function(){const a=JSON.parse(decodeURIComponent(this.dataset.row));scanned={display_name:a.display_name,barcode:a.barcode_snapshot,tracking_type:a.tracking_type,allocations:[a]};openAction('inspect',a.id)});
function loadCustomers(){const body=$('#techCustomerTable tbody');if(!body.length)return;$.getJSON(base+'teknisi/customer_equipment_data').done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td><strong>'+esc(a.customer_name)+'</strong></td><td>'+qty(a.item_count)+'</td><td><strong>'+qty(a.total_qty)+'</strong></td><td>'+qty(a.company_qty)+'</td><td>'+qty(a.customer_qty)+'</td><td>'+qty(a.installed_qty)+'</td><td>'+qty(a.good_qty)+'</td><td><div class="tech-doc-actions"><button class="btn btn-outline-primary btn-sm tech-customer-group" data-id="'+Number(a.customer_id)+'" data-name="'+esc(a.customer_name)+'"><i class="bi bi-eye"></i> Detail</button><button class="btn btn-outline-secondary btn-sm tech-customer-notes" data-id="'+Number(a.customer_id)+'" data-name="'+esc(a.customer_name)+'"><i class="bi bi-card-text"></i> Keterangan</button></div></td></tr>').join(''):'<tr><td colspan="8" class="tech-empty">Belum ada customer yang mempunyai barang tercatat.</td></tr>')}).fail(()=>body.html('<tr><td colspan="8" class="tech-empty text-danger">Ringkasan barang customer gagal dimuat.</td></tr>'))}
let activeCustomerId=0,activeCustomerName='';
function customerPosition(a){if(a.custodian_type==='technician')return'Teknisi: '+(a.technician_name||'-');if(a.custodian_type==='customer')return(a.usage_status==='installed'?'Terpasang di ':'Tersimpan di ')+(a.customer_name||'Customer');if(a.custodian_type==='quarantine')return'Karantina: '+(a.warehouse_name||'-');if(a.custodian_type==='warehouse')return'Gudang perusahaan: '+(a.warehouse_name||'-');if(a.custodian_type==='service_center')return'Pusat perbaikan: '+(a.warehouse_name||'-');return a.custodian_type||'-'}
function loadCustomerGroup(){if(!activeCustomerId)return;const body=$('#techCustomerGroupBody'),status=$('#techCustomerStatus').val()||'all';body.html('<tr><td colspan="7" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat detail...</td></tr>');$.getJSON(base+'teknisi/customer_equipment_customer_data/'+activeCustomerId,{status:status}).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td><strong>'+esc(a.display_name)+'</strong><br><small>'+esc(a.tracking_type)+'</small></td><td>'+esc(a.barcode_snapshot)+'<br><small>'+esc(a.serial_number||'-')+'</small></td><td>'+esc(a.ownership_type==='company'?'Milik Perusahaan':a.ownership_type==='customer'?'Milik Customer':'Perusahaan / Non-Persediaan')+'</td><td>'+qty(a.qty)+'</td><td>'+esc(customerPosition(a))+'</td><td><span class="tech-status">'+esc(labels[a.usage_status]||a.usage_status)+'</span></td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td></tr>').join(''):'<tr><td colspan="7" class="tech-empty">Tidak ada barang sesuai status yang dipilih.</td></tr>')}).fail(x=>body.html('<tr><td colspan="7" class="tech-empty text-danger">'+esc((x.responseJSON&&x.responseJSON.message)||'Detail barang gagal dimuat.')+'</td></tr>'))}
$(document).on('click','.tech-customer-group',function(){activeCustomerId=Number(this.dataset.id);activeCustomerName=this.dataset.name||'Customer';$('#techCustomerGroupTitle').text(activeCustomerName);$('#techCustomerStatus').val('all');modal('techCustomerGroupModal',true);loadCustomerGroup()});
$('#techCustomerApplyStatus').on('click',loadCustomerGroup);$('#techCustomerStatus').on('change',loadCustomerGroup);
$('#techCustomerPdf').on('click',function(){if(!activeCustomerId)return;preview(base+'teknisi/customer_equipment_customer_pdf/'+activeCustomerId+'?status='+encodeURIComponent($('#techCustomerStatus').val()||'all'),'application/pdf')});
$(document).on('click','.tech-customer-notes',function(){const customerId=Number(this.dataset.id),customerName=this.dataset.name||'Customer',body=$('#techCustomerNotesTable tbody');if(!customerId)return;if(customerNotesTable){customerNotesTable.destroy();customerNotesTable=null}$('#techCustomerNotesTitle').text('Log Keterangan · '+customerName);$('#techCustomerNotesPdf').attr('href',base+'teknisi/customer_equipment_notes_pdf/'+customerId);body.html('<tr><td colspan="7" class="tech-empty"><span class="spinner-border spinner-border-sm me-2"></span>Memuat log keterangan...</td></tr>');modal('techCustomerNotesModal',true);$.getJSON(base+'teknisi/customer_equipment_notes_data/'+customerId).done(r=>{const rows=r.data||[];body.html(rows.length?rows.map(a=>'<tr><td>'+esc(a.document_date)+'<br><small>'+esc(a.document_no)+'</small></td><td><strong>'+esc(a.display_name)+'</strong></td><td>'+esc(a.barcode_snapshot||'-')+'<br><small>SN '+esc(a.serial_number||'-')+'</small></td><td>'+esc(actionLabels[a.document_type]||a.document_type)+'</td><td>'+qty(a.qty)+'</td><td>'+esc(a.technician_name||a.actor_name||'-')+'</td><td class="text-wrap" style="min-width:240px;white-space:pre-wrap">'+esc(a.note_text||'-')+'</td></tr>').join(''):'<tr><td colspan="7" class="tech-empty">Belum ada log keterangan untuk customer ini.</td></tr>');if(rows.length)customerNotesTable=createSearchTable('#techCustomerNotesTable',[[0,'desc']],false)}).fail(x=>body.html('<tr><td colspan="7" class="tech-empty text-danger">'+esc((x.responseJSON&&x.responseJSON.message)||'Log keterangan gagal dimuat.')+'</td></tr>'))});
$('#techCustomerLookupForm').on('submit',function(e){e.preventDefault();const query=$('#techCustomerLookup').val().trim(),target=$('#techCustomerLookupResult');if(!query)return toast(false,'Scan barcode/SN atau isi nama barang customer.');const btn=$(this).find('button').prop('disabled',true);$.getJSON(base+'teknisi/equipment_search',{q:query,mode:'customer'}).done(r=>{const rows=r.data||[];if(!rows.length){target.addClass('d-none').empty();$('#techCustomerItemForm [name=external_barcode]').val(query);document.getElementById('techCustomerItemForm').scrollIntoView({behavior:'smooth',block:'start'});toast(true,'Data lama tidak ditemukan. Barcode sudah dimasukkan ke form baru; lengkapi data wajib saja.');return}target.removeClass('d-none').html('<div class="tech-warning"><strong>Barang sudah pernah dicatat.</strong><br><small>Pilih data berikut; identitas tidak perlu dimasukkan ulang.</small></div><div class="tech-search-results">'+rows.map(row=>'<a class="tech-search-option" href="'+base+'teknisi?scan='+encodeURIComponent(row.lookup_code)+'#scanner"><span><strong>'+esc(row.display_name)+'</strong><small>'+esc(row.barcode)+(row.serial_number?' · SN '+esc(row.serial_number):'')+' · '+esc(row.customer_name||'-')+'</small></span><span><b>Buka Pergerakan</b><small>'+esc(labels[row.ownership_type]||row.ownership_type)+'</small></span></a>').join('')+'</div>')}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Pencarian barang customer gagal.')).always(()=>btn.prop('disabled',false))});
$(document).on('click','.tech-customer-detail',function(){$.getJSON(base+'teknisi/customer_equipment_detail/'+this.dataset.id).done(r=>{const d=r.result,positions=(d.allocations||[]).map(a=>'<tr><td>'+esc(positionText(a))+'</td><td>'+esc(labels[a.usage_status]||a.usage_status)+'</td><td>'+esc(labels[a.condition_status]||a.condition_status)+'</td><td>'+qty(a.qty)+'</td></tr>').join(''),files=(perms.attachment?d.attachments||[]:[]).map(f=>'<button class="btn btn-outline-secondary btn-sm tech-attachment" data-url="'+base+'accountingattachments/preview/'+f.id+'" data-mime="'+esc(f.mime_type)+'"><i class="bi '+(f.mime_type==='application/pdf'?'bi-file-pdf':'bi-image')+'"></i> '+esc(f.original_name)+'</button>').join(' ');$('#techDocumentBody').html('<div class="tech-modal-summary"><div><small>Barang</small><strong>'+esc(d.equipment_name)+'</strong><br>'+esc([d.brand,d.model].filter(Boolean).join(' ')||'-')+'</div><div><small>Barcode / Serial</small>'+esc(d.external_barcode)+'<br>'+esc(d.serial_number||'-')+'</div><div><small>Pemilik</small>'+esc(d.owner_name||'-')+'<br>'+esc(labels[d.ownership_type]||d.ownership_type)+'</div></div><p class="mt-3"><strong>Otorisasi penggunaan ulang:</strong> '+(Number(d.deployment_allowed)?'Diizinkan · '+esc(d.authorization_reference||'-'):'Tidak diizinkan')+'</p><div class="tech-table-wrap"><table class="table tech-table"><thead><tr><th>Posisi</th><th>Status</th><th>Kondisi</th><th>Qty</th></tr></thead><tbody>'+positions+'</tbody></table></div><div class="tech-actions"><strong class="w-100">Foto / Dokumen</strong>'+(files||'<span class="text-muted">Tidak ada lampiran atau akses tidak diberikan.</span>')+'</div>');modal('techDocumentModal',true)}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Detail barang customer gagal dimuat.'))});
$('#techCustomerItemForm').on('submit',async function(e){e.preventDefault();if(!(await confirmPost('Simpan barang customer?')))return;const form=this,btn=$(form).find('[type=submit]').prop('disabled',true);$.ajax({url:base+'teknisi/register_customer_equipment',method:'POST',data:new FormData(form),processData:false,contentType:false,dataType:'json'}).done(r=>{if(!r.status)return toast(false,r.message);toast(true,r.message);form.reset();clearFileBox($(form).find('[data-filebox]')[0]);loadCustomers()}).fail(x=>toast(false,(x.responseJSON&&x.responseJSON.message)||'Register gagal disimpan.')).always(()=>btn.prop('disabled',false))});
const selections=new WeakMap();function setupFileBox(box){if(!box)return;const input=box.querySelector('input[type=file]');if(!input||input.dataset.ready)return;input.dataset.ready='1';selections.set(box,[]);input.addEventListener('change',()=>{const all=(selections.get(box)||[]).concat(Array.from(input.files));selections.set(box,all.slice(0,10));syncFiles(box)})}
function syncFiles(box){const input=box.querySelector('input[type=file]'),files=selections.get(box)||[],add=box.querySelector('.tech-add-file');box.querySelectorAll('.tech-thumb').forEach(x=>x.remove());files.forEach((f,i)=>{const el=document.createElement('div');el.className='tech-thumb';const isPdf=f.type==='application/pdf';if(isPdf)el.innerHTML='<div class="tech-pdf"><i class="bi bi-file-pdf fs-2 text-danger"></i></div>';else{const img=document.createElement('img');img.src=URL.createObjectURL(f);img.alt=f.name;el.appendChild(img)}const x=document.createElement('button');x.type='button';x.innerHTML='&times;';x.title='Hapus';x.addEventListener('click',()=>{const next=(selections.get(box)||[]).filter((_,n)=>n!==i);selections.set(box,next);syncFiles(box)});el.appendChild(x);el.addEventListener('click',e=>{if(e.target===x)return;const url=URL.createObjectURL(f);preview(url,f.type)});box.insertBefore(el,add)});const dt=new DataTransfer();files.forEach(f=>dt.items.add(f));input.files=dt.files}
function clearFileBox(box){if(!box)return;selections.set(box,[]);syncFiles(box)}
document.querySelectorAll('[data-filebox]').forEach(setupFileBox);
$(function(){if(Array.isArray(cfg.targetTechnicians)&&$('#techActionForm [name=target_technician_id]').length)$('#techActionForm [name=target_technician_id]').html('<option value="">Pilih karyawan</option>'+cfg.targetTechnicians.map(t=>'<option value="'+Number(t.id)+'">'+esc(t.label)+'</option>').join(''));initEmployeeSearch(document);if($('#techTechniciansTable tbody tr').length&&!$('#techTechniciansTable tbody td[colspan]').length)technicianTable=createSearchTable('#techTechniciansTable',[[0,'asc']]);if(!perms.condition)$('#techActionForm [name=result_status] option[value=damaged],#techActionForm [name=result_status] option[value=lost],#techActionForm [name=result_status] option[value=scrapped]').remove();loadDocuments();loadAllocs();loadInspection();loadCustomers();const initial=new URLSearchParams(location.search).get('scan');if(initial&&$('#techScanCode').length){$('#techScanCode').val(initial);$('#techScanForm').trigger('submit')}});
})(jQuery);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long