/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ .flagged-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 4px 0 14px; } .flagged-live { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #334155; } /* Pulses while the feed is polling; goes flat and grey when paused. */ .live-dot { width: 9px; height: 9px; border-radius: 50%; background: #16a34a; box-shadow: 0 0 0 0 rgb(22 163 74 / 60%); animation: live-pulse 2s infinite; &.paused { background: #94a3b8; animation: none; box-shadow: none; } } @keyframes live-pulse { 0% { box-shadow: 0 0 0 0 rgb(22 163 74 / 60%); } 70% { box-shadow: 0 0 0 8px rgb(22 163 74 / 0%); } 100% { box-shadow: 0 0 0 0 rgb(22 163 74 / 0%); } } .live-label { font-weight: 700; text-transform: uppercase; font-size: 11px; letter-spacing: 0.05em; color: #16a34a; .live-dot.paused + & { color: #64748b; } } .live-updated { color: #64748b; font-size: 12px; } .live-spinner { display: inline-block; } .flagged-actions { display: flex; align-items: center; gap: 4px; } .new-alert-pill { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border: 1px solid #fdba74; border-radius: 999px; background: #fff7ed; color: #c2410c; font-family: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; mat-icon { font-size: 16px; width: 16px; height: 16px; } &:hover { background: #ffedd5; } } .flagged-error { display: flex; align-items: center; gap: 8px; padding: 10px 12px; margin-bottom: 12px; border: 1px solid #fecaca; border-radius: 6px; background: #fef2f2; color: #b91c1c; font-size: 13px; mat-icon { font-size: 20px; width: 20px; height: 20px; } } /* Chips that narrow the feed by review status. */ .status-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 0 0 14px; } .status-filters-label { font-size: 11px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; } .status-chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 13px; border: 1px solid #cbd5e1; background: #fff; border-radius: 999px; font-size: 12.5px; font-weight: 600; color: #334155; cursor: pointer; font-family: inherit; transition: all 0.15s; &:hover:not(:disabled) { border-color: #1a237e; color: #1a237e; } &.active { background: #1a237e; border-color: #1a237e; color: #fff; } &:disabled { opacity: 0.45; cursor: not-allowed; } } .status-chip-n { font-size: 11px; font-weight: 700; padding: 1px 7px; border-radius: 999px; background: #f1f5f9; color: #475569; .status-chip.active & { background: rgb(255 255 255 / 22%); color: #fff; } } /* Severity mix of the visible rows. Colours match the Rules Master risk badges so the same category reads the same across both tabs. */ .risk-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; padding: 0 0 16px; } /* A button, not a div: each tile narrows the table to its own severity band. */ .risk-tile { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 12px 14px; border: 1px solid #e2e8f0; border-left-width: 4px; border-radius: 8px; background: #fff; font-family: inherit; text-align: left; cursor: pointer; transition: box-shadow 0.15s, transform 0.1s; &:hover:not(:disabled) { box-shadow: 0 2px 8px rgb(15 23 42 / 12%); } &:active:not(:disabled) { transform: translateY(1px); } /* A band with nothing in it would filter to an empty table. */ &:disabled { opacity: 0.5; cursor: default; } } /* The band currently narrowing the table. The ring reads on every tile colour, where a background change would fight the severity tint each tile already carries. */ .risk-tile.tile-active { box-shadow: 0 0 0 2px #1a237e; } .risk-tile-n { font-size: 22px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; } .risk-tile-l { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; } .risk-tile.risk-high { border-left-color: #dc2626; background: #fef2f2; .risk-tile-n { color: #b91c1c; } } .risk-tile.risk-medium { border-left-color: #f59e0b; background: #fffbeb; .risk-tile-n { color: #b45309; } } .risk-tile.risk-low { border-left-color: #16a34a; background: #f0fdf4; .risk-tile-n { color: #15803d; } } .risk-tile.risk-none { border-left-color: #94a3b8; background: #f8fafc; .risk-tile-n { color: #475569; } } .risk-tile.risk-total { border-left-color: #1a237e; background: #f8fafc; .risk-tile-n { color: #1a237e; } } .flagged-table { width: 100%; border-collapse: separate; border-spacing: 0; border-radius: 8px; overflow: hidden; border: 1px solid #e2e8f0; th { background-color: #f1f5f9; color: #475569; font-weight: 600; font-size: 13px; padding: 16px; text-transform: uppercase; letter-spacing: 0.03em; } td { padding: 14px 16px; font-size: 13px; color: #1e293b; border-bottom: 1px solid #f1f5f9; } } .alert-row { cursor: pointer; &:hover { background: #f8fafc; } } /* The open row and its pane read as one block. */ .alert-row.row-expanded { background: #f8fafc; td { border-bottom-color: transparent; } } /* A newly arrived alert stays tinted until acknowledged. */ .row-new { background: #fffbeb; &:hover { background: #fef3c7; } } .col-center { text-align: center; } .col-right { text-align: right; } .raised-cell { white-space: nowrap; } .raised-time { font-variant-numeric: tabular-nums; } .new-badge { display: inline-block; margin-left: 8px; padding: 1px 6px; border-radius: 4px; background: #f59e0b; color: #fff; font-size: 10px; font-weight: 700; letter-spacing: 0.04em; } .txn-type { display: inline-block; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 700; letter-spacing: 0.03em; background: #e2e8f0; color: #475569; &.type-savings { background: #dbeafe; color: #1d4ed8; } &.type-loan { background: #ede9fe; color: #6d28d9; } &.type-transfer { background: #ccfbf1; color: #0f766e; } } .txn-id { margin-left: 8px; color: #64748b; font-size: 12px; font-variant-numeric: tabular-nums; } /* Two-line cells stack inside a wrapper div. Making the itself a flex container drops its display:table-cell, which collapses the row and pushes later columns onto their own line. */ .stacked-cell { display: flex; flex-direction: column; gap: 2px; } .cell-sub { color: #64748b; font-size: 12px; } /* Quieter than the governing rule: it is context on one case, not a second case. */ .rule-also { margin-top: 3px; color: #92400e; background: #fffbeb; border: 1px solid #fde68a; border-radius: 4px; padding: 1px 6px; font-size: 11px; align-self: flex-start; } /* Says how many rules fired and opens the detail. Replaces the block of codes that used to sit in this cell: the codes belong in the pane, where each can carry its own severity. */ .rule-more { display: inline-flex; align-items: center; gap: 3px; margin-top: 4px; padding: 2px 8px 2px 4px; align-self: flex-start; border: 1px solid #fde68a; border-radius: 999px; background: #fffbeb; color: #92400e; font-family: inherit; font-size: 11px; font-weight: 700; cursor: pointer; mat-icon { font-size: 15px; width: 15px; height: 15px; } &:hover { background: #fef3c7; } } /* ── Expanded detail ─────────────────────────────────────────────────────────────────────── */ .detail-row { height: 0; } .detail-cell { padding: 0 !important; border-bottom: none !important; } /* Animating max-height rather than height: the pane's height depends on how many rules fired, and a transition needs a figure to move between. */ .detail-pane { max-height: 0; overflow: hidden; transition: max-height 0.2s ease; background: #f8fafc; /* Generous enough for a transaction that tripped every rule it could, plus the action bar. Too low silently clips the last rule, which is the one an officer most needs to see. */ &.detail-open { max-height: 1200px; } } .detail-inner { padding: 14px 16px 16px 60px; border-bottom: 1px solid #e2e8f0; } .detail-head { display: flex; align-items: center; gap: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; margin-bottom: 10px; } .rule-select-all { border: none; background: none; padding: 0; font-family: inherit; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: #1a237e; cursor: pointer; &:hover { text-decoration: underline; } } /* A rule that already carries a verdict is context, not a choice. */ .detail-rule-decided { opacity: 0.75; } .rule-decided-icon { flex: none; font-size: 18px; width: 18px; height: 18px; color: #15803d; cursor: help; &.decided-escalated { color: #b91c1c; } /* Closed alongside another rule rather than judged on its own — stated, but not as a verdict. */ &.decided-carried { color: #94a3b8; } /* A statutory return: not the officer's decision at all. */ &.decided-statutory { color: #475569; } } /* Where a statutory rule goes, said next to the rule rather than left to be inferred from a missing checkbox. */ .ctr-tag { margin-left: 6px; padding: 1px 6px; border-radius: 4px; background: #e2e8f0; color: #334155; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; white-space: nowrap; } .rule-verdict { margin-left: 6px; padding: 1px 6px; border-radius: 4px; background: #dcfce7; color: #15803d; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; &.verdict-escalated { background: #fee2e2; color: #b91c1c; } /* Wins over the disposition colours above: this rule was not itself the finding, and colouring it like one would claim the officer condemned it. */ &.verdict-carried { background: #f1f5f9; color: #64748b; } } /* Acts on the ticked rules only, and sits with them rather than at the top of the table. */ .rule-action-bar { display: flex; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border: 1px solid #bfdbfe; border-radius: 8px; background: #eff6ff; } .rule-action-count { font-size: 12.5px; font-weight: 700; color: #1e3a8a; white-space: nowrap; } .detail-rule { display: flex; align-items: flex-start; gap: 10px; padding: 7px 10px; border-radius: 6px; background: #fff; border: 1px solid #e2e8f0; & + & { margin-top: 6px; } } /* The rule the alert was raised under, and the one the disposition is recorded against. */ .detail-rule-governing { border-color: #cbd5e1; box-shadow: inset 3px 0 0 #1a237e; } .detail-rule-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; } .detail-rule-code { font-size: 12.5px; font-weight: 700; color: #1e293b; } .detail-rule-desc { font-size: 12px; color: #64748b; } .governing-tag { margin-left: 6px; padding: 1px 6px; border-radius: 4px; background: #e0e7ff; color: #3730a3; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; } /* Severity of one rule inside the pane. Same colours as the summary tiles, so a band reads the same wherever it appears. */ .sev-chip { flex: none; min-width: 62px; text-align: center; padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; &.sev-high { background: #fee2e2; color: #b91c1c; } &.sev-medium { background: #fef3c7; color: #b45309; } &.sev-low { background: #dcfce7; color: #15803d; } &.sev-none { background: #e2e8f0; color: #475569; } } /* Why this transaction can or cannot be decided in a batch, said where the officer is deciding. */ .detail-foot { margin-top: 10px; font-size: 12px; color: #475569; font-style: italic; } .rule-code { font-weight: 600; } .rule-cell { max-width: 300px; } .amount-cell { font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; } .alert-status { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 700; letter-spacing: 0.03em; &.status-raised { background: #fef3c7; color: #92400e; } &.status-under_review { background: #dbeafe; color: #1d4ed8; } &.status-escalated { background: #fee2e2; color: #b91c1c; } &.status-whitelisted { background: #dcfce7; color: #15803d; } } .actions-cell { white-space: nowrap; } .action-group { display: inline-flex; align-items: center; gap: 2px; } .action-btn { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px; margin: 2px; border-radius: 6px; border: 1px solid transparent; font-family: inherit; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s; mat-icon { font-size: 15px; width: 15px; height: 15px; } } .action-whitelist { border-color: #bbf7d0; background: #f0fdf4; color: #15803d; &:hover { background: #dcfce7; border-color: #86efac; } } .action-suspicious { border-color: #fecaca; background: #fef2f2; color: #b91c1c; &:hover { background: #fee2e2; border-color: #fca5a5; } } .reviewed-cell { display: inline-flex; align-items: center; gap: 6px; justify-content: center; } .reviewed-by { color: #94a3b8; font-size: 12px; font-style: italic; } .reviewed-evidence-icon { font-size: 16px; width: 16px; height: 16px; color: #64748b; cursor: help; } /* Mirrors the FIU-IND STR register badge, so the same state reads the same in both screens. */ .str-routed { padding: 2px 7px; border-radius: 4px; background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c; font-size: 11px; font-weight: 700; white-space: nowrap; } ::ng-deep .evidence-tooltip { max-width: 320px; white-space: pre-wrap; font-size: 12px; line-height: 1.5; } .no-alerts-message { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px; color: #64748b; gap: 12px; text-align: center; mat-icon { font-size: 48px; width: 48px; height: 48px; } } .flagged-paginator { background: #fff; border-top: 1px solid #e2e8f0; } /* Batch disposition bar — only rendered while rows are ticked. */ .bulk-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; margin-bottom: 10px; border: 1px solid #bfdbfe; border-radius: 10px; background: #eff6ff; } .bulk-count { font-size: 13px; font-weight: 700; color: #1e3a8a; white-space: nowrap; } /* Says why some rows cannot be ticked, where the officer is looking rather than in a tooltip. */ .bulk-note { font-size: 12px; color: #475569; } .bulk-spacer { flex: 1; } .select-cell { width: 44px; padding-right: 0; } /* Stands in for the checkbox on a row that cannot be batched, so the column still reads as a column rather than leaving a hole the eye has to account for. */ .select-na { display: inline-block; width: 18px; text-align: center; color: #cbd5e1; cursor: help; user-select: none; } /* Day picker. Sized and bordered to sit level with the status chips beside it, so the two rows read as one control strip rather than a form field dropped above a set of buttons. */ .day-input { padding: 5px 11px; border: 1px solid #cbd5e1; border-radius: 999px; background: #fff; font-family: inherit; font-size: 12.5px; font-weight: 600; color: #334155; cursor: pointer; &:hover { border-color: #1a237e; } &:focus { outline: none; border-color: #1a237e; } } /* What the chosen mode resolved to. A week picked by naming one day inside it is otherwise invisible — the officer has no way to tell which Monday-to-Sunday span they are looking at. */ .period-resolved { font-size: 12px; color: #64748b; font-variant-numeric: tabular-nums; } /* Sits with the pause and refresh controls, green because it produces something the officer takes away rather than changing what is on screen. */ .export-btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 13px; border: 1px solid #bbf7d0; border-radius: 999px; background: #f0fdf4; color: #15803d; font-family: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer; mat-icon { font-size: 16px; width: 16px; height: 16px; } &:hover:not(:disabled) { background: #dcfce7; border-color: #86efac; } &:disabled { opacity: 0.5; cursor: not-allowed; } } /* Held by another officer. Grey, not red: nobody has done anything wrong, the case is simply somebody else's. */ .held-icon { font-size: 16px; width: 16px; height: 16px; color: #94a3b8; cursor: help; } .held-by { color: #64748b; font-size: 12px; font-style: italic; } /* Rule frequency strip. Sits under the filters as a reading aid, not a control — clicking a rule would be a fourth filter competing with the three above it. */ .rule-freq { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 0 14px; } .rule-freq-item { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border: 1px solid #e2e8f0; border-radius: 999px; background: #f8fafc; font-size: 11px; font-weight: 600; color: #475569; } .rule-freq-n { padding: 0 6px; border-radius: 999px; background: #e2e8f0; color: #1e293b; font-variant-numeric: tabular-nums; }