Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_admin_user.html
Diff
/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/TEMPLATES/tf_admin_user.html
added on local at 2026-07-11 18:33:09
Added
+217
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 5338e5acedf5
to 5338e5acedf5
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | <style> | |
| 2 | .au-grid { | |
| 3 | display: grid; | |
| 4 | grid-template-columns: 320px 1fr; | |
| 5 | gap: 16px; | |
| 6 | margin-top: 14px; | |
| 7 | } | |
| 8 | @media (max-width: 1000px) { .au-grid { grid-template-columns: 1fr; } } | |
| 9 | ||
| 10 | .au-card { | |
| 11 | background: var(--col-surface-1, var(--col-bg-2)); | |
| 12 | border: 1px solid var(--col-border); | |
| 13 | border-radius: 12px; | |
| 14 | padding: 18px; | |
| 15 | } | |
| 16 | .au-card-title { | |
| 17 | font-size: 10px; font-weight: 700; | |
| 18 | letter-spacing: 1.5px; text-transform: uppercase; | |
| 19 | color: var(--col-text-dim); | |
| 20 | margin-bottom: 12px; | |
| 21 | } | |
| 22 | .au-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; font-size: 13px; border-bottom: 1px solid var(--col-border); } | |
| 23 | .au-row:last-child { border-bottom: none; } | |
| 24 | .au-row .lbl { color: var(--col-text-dim); } | |
| 25 | .au-row .val { color: var(--col-text); font-weight: 600; text-align: right; word-break: break-word; } | |
| 26 | .au-row .val.mono { font-family: var(--font-mono, monospace); font-size: 12px; } | |
| 27 | ||
| 28 | .au-pill { | |
| 29 | font-size: 9px; font-weight: 700; | |
| 30 | padding: 3px 8px; border-radius: 5px; | |
| 31 | letter-spacing: 1px; text-transform: uppercase; | |
| 32 | } | |
| 33 | .au-pill.ok { background: rgba(34,197,94,0.18); color: #4ade80; } | |
| 34 | .au-pill.warn { background: rgba(245,158,11,0.20); color: #fbbf24; } | |
| 35 | .au-pill.danger { background: rgba(239,68,68,0.20); color: #f87171; } | |
| 36 | .au-pill.is-admin { background: rgba(124,58,237,0.18); color: #a78bfa; } | |
| 37 | ||
| 38 | .au-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; } | |
| 39 | .au-actions form { margin: 0; } | |
| 40 | .au-actions button { | |
| 41 | width: 100%; | |
| 42 | background: var(--col-surface-2, var(--col-bg-3)); | |
| 43 | border: 1px solid var(--col-border); | |
| 44 | color: var(--col-text); | |
| 45 | padding: 9px 12px; | |
| 46 | border-radius: 8px; | |
| 47 | cursor: pointer; | |
| 48 | font-size: 13px; | |
| 49 | font-weight: 600; | |
| 50 | text-align: left; | |
| 51 | transition: all 0.12s; | |
| 52 | } | |
| 53 | .au-actions button:hover { border-color: var(--col-primary); } | |
| 54 | .au-actions .danger button { color: #fca5a5; border-color: rgba(239,68,68,0.30); } | |
| 55 | .au-actions .danger button:hover { background: rgba(239,68,68,0.10); border-color: rgba(239,68,68,0.65); } | |
| 56 | .au-actions .primary button { background: var(--col-primary); border-color: transparent; color: #fff; } | |
| 57 | ||
| 58 | .au-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; } | |
| 59 | .au-table th { text-align: left; font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-dim); padding: 8px 10px; font-weight: 700; border-bottom: 1px solid var(--col-border); } | |
| 60 | .au-table td { padding: 9px 10px; border-bottom: 1px solid var(--col-border); color: var(--col-text); } | |
| 61 | .au-table tr:last-child td { border-bottom: none; } | |
| 62 | .au-table .mono { font-family: var(--font-mono, monospace); color: var(--col-text); } | |
| 63 | ||
| 64 | .au-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 14px; } | |
| 65 | .au-stat { padding: 12px 14px; background: var(--col-surface-2, var(--col-bg-3)); border: 1px solid var(--col-border); border-radius: 9px; } | |
| 66 | .au-stat .n { font-size: 22px; font-weight: 800; color: var(--col-text); font-family: var(--font-mono, monospace); line-height: 1.1; } | |
| 67 | .au-stat .lb { font-size: 10px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: var(--col-text-dim); margin-top: 4px; } | |
| 68 | </style> | |
| 69 | ||
| 70 | <div class="page-header"> | |
| 71 | <div> | |
| 72 | <p class="subtitle" style="text-transform:uppercase;letter-spacing:1.5px;font-size:11px;font-weight:700;color:var(--col-text-dim)">Admin · User detail</p> | |
| 73 | <h1>$target_display_name</h1> | |
| 74 | <p class="subtitle">$target_email · user #$target_id</p> | |
| 75 | </div> | |
| 76 | <div> | |
| 77 | <a href="/admin_users.cgi" class="btn btn-secondary">← Back to search</a> | |
| 78 | </div> | |
| 79 | </div> | |
| 80 | ||
| 81 | <div class="au-grid"> | |
| 82 | ||
| 83 | <!-- LEFT: account + admin actions --> | |
| 84 | <div> | |
| 85 | <div class="au-card"> | |
| 86 | <div class="au-card-title">Account</div> | |
| 87 | <div class="au-row"><span class="lbl">Email</span><span class="val">$target_email</span></div> | |
| 88 | <div class="au-row"><span class="lbl">Name</span><span class="val">$target_display_name</span></div> | |
| 89 | [if:$target_job_title]<div class="au-row"><span class="lbl">Job title</span><span class="val">$target_job_title</span></div>[/if] | |
| 90 | [if:$target_phone]<div class="au-row"><span class="lbl">Phone</span><span class="val mono">$target_phone</span></div>[/if] | |
| 91 | <div class="au-row"><span class="lbl">Role</span><span class="val"> | |
| 92 | [if:$target_is_super_admin]<span class="au-pill is-admin">Super admin</span>[/if] | |
| 93 | [if:!$target_is_super_admin]$target_role_display[/if] | |
| 94 | </span></div> | |
| 95 | <div class="au-row"><span class="lbl">Status</span><span class="val"> | |
| 96 | [if:$is_active]<span class="au-pill ok">Active</span>[/if] | |
| 97 | [if:$is_suspended]<span class="au-pill warn">Suspended</span>[/if] | |
| 98 | [if:$is_closed]<span class="au-pill danger">Closed</span>[/if] | |
| 99 | [if:$is_pending_verification]<span class="au-pill warn">Pending verification</span>[/if] | |
| 100 | </span></div> | |
| 101 | <div class="au-row"><span class="lbl">Email verified</span><span class="val">$target_email_verified</span></div> | |
| 102 | <div class="au-row"><span class="lbl">2FA</span><span class="val">$target_2fa</span></div> | |
| 103 | <div class="au-row"><span class="lbl">Timezone</span><span class="val">$target_timezone</span></div> | |
| 104 | <div class="au-row"><span class="lbl">Locale</span><span class="val">$target_locale</span></div> | |
| 105 | <div class="au-row"><span class="lbl">Joined</span><span class="val mono"><span class="ts" data-ts="$target_created_epoch" data-fmt="datetime">$target_created_at</span></span></div> | |
| 106 | <div class="au-row"><span class="lbl">Last login</span><span class="val mono"><span class="ts" data-ts="$target_last_login_epoch" data-fmt="datetime">$target_last_login_at</span></span></div> | |
| 107 | </div> | |
| 108 | ||
| 109 | <div class="au-card" style="margin-top:14px"> | |
| 110 | <div class="au-card-title">Admin actions</div> | |
| 111 | <div class="au-actions"> | |
| 112 | [if:!$is_self] | |
| 113 | <form method="POST" action="/admin_action.cgi" class="primary"> | |
| 114 | <input type="hidden" name="act" value="impersonate"> | |
| 115 | <input type="hidden" name="target_user_id" value="$target_id"> | |
| 116 | <button type="submit">Impersonate user</button> | |
| 117 | </form> | |
| 118 | [/if] | |
| 119 | [if:$is_self] | |
| 120 | <div style="font-size:11px;color:var(--col-text-dim);padding:8px 10px">This is your own account — nothing to impersonate.</div> | |
| 121 | [/if] | |
| 122 | ||
| 123 | [if:$is_active] | |
| 124 | <form method="POST" action="/admin_action.cgi" | |
| 125 | onsubmit="return confirm('Suspend this account? They will be logged out immediately and unable to sign in until reactivated.')"> | |
| 126 | <input type="hidden" name="act" value="suspend_user"> | |
| 127 | <input type="hidden" name="target_user_id" value="$target_id"> | |
| 128 | <button type="submit">Suspend account</button> | |
| 129 | </form> | |
| 130 | [/if] | |
| 131 | ||
| 132 | [if:$is_suspended] | |
| 133 | <form method="POST" action="/admin_action.cgi"> | |
| 134 | <input type="hidden" name="act" value="reactivate_user"> | |
| 135 | <input type="hidden" name="target_user_id" value="$target_id"> | |
| 136 | <button type="submit">Reactivate account</button> | |
| 137 | </form> | |
| 138 | [/if] | |
| 139 | ||
| 140 | [if:!$is_self] | |
| 141 | <form method="POST" action="/admin_action.cgi" class="danger" | |
| 142 | onsubmit="return confirm('Close this account permanently? Data stays in the database but they can never sign in again.')"> | |
| 143 | <input type="hidden" name="act" value="close_user"> | |
| 144 | <input type="hidden" name="target_user_id" value="$target_id"> | |
| 145 | <button type="submit">Close account</button> | |
| 146 | </form> | |
| 147 | [/if] | |
| 148 | </div> | |
| 149 | </div> | |
| 150 | </div> | |
| 151 | ||
| 152 | <!-- RIGHT: workspace + projects + tasks + time --> | |
| 153 | <div> | |
| 154 | [if:$has_company] | |
| 155 | <div class="au-card"> | |
| 156 | <div class="au-card-title">Workspace</div> | |
| 157 | <div class="au-row"><span class="lbl">Name</span><span class="val"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:$company_color;margin-right:6px;vertical-align:middle"></span>$company_name</span></div> | |
| 158 | <div class="au-row"><span class="lbl">Plan</span><span class="val">$company_plan</span></div> | |
| 159 | <div class="au-row"><span class="lbl">Joined</span><span class="val mono"><span class="ts" data-ts="$company_joined_epoch" data-fmt="datetime">$company_joined</span></span></div> | |
| 160 | <div class="au-row"><span class="lbl">Actions</span><span class="val"><a href="/admin_company.cgi?id=$company_id" style="color:var(--col-primary)">Open workspace →</a></span></div> | |
| 161 | </div> | |
| 162 | [/if] | |
| 163 | ||
| 164 | <div class="au-card" style="margin-top:14px"> | |
| 165 | <div class="au-card-title">Activity stats</div> | |
| 166 | <div class="au-stat-grid"> | |
| 167 | <div class="au-stat"><div class="n">$p_active</div><div class="lb">Active projects</div></div> | |
| 168 | <div class="au-stat"><div class="n">$t_open</div><div class="lb">Open tasks</div></div> | |
| 169 | <div class="au-stat"><div class="n">$t_done</div><div class="lb">Tasks completed</div></div> | |
| 170 | <div class="au-stat"><div class="n">$tt_30d_hours</div><div class="lb">Hours (30d)</div></div> | |
| 171 | <div class="au-stat"><div class="n">$tt_life_hours</div><div class="lb">Hours (lifetime)</div></div> | |
| 172 | <div class="au-stat"><div class="n">$tt_billable_hours</div><div class="lb">Billable hours</div></div> | |
| 173 | </div> | |
| 174 | </div> | |
| 175 | ||
| 176 | <div class="au-card" style="margin-top:14px"> | |
| 177 | <div class="au-card-title">Projects owned · $p_total total</div> | |
| 178 | [if:$has_projects] | |
| 179 | <table class="au-table"> | |
| 180 | <thead><tr><th>Name</th><th>Status</th><th>Priority</th><th>Due</th></tr></thead> | |
| 181 | <tbody> | |
| 182 | [loop:@projects] | |
| 183 | <tr> | |
| 184 | <td><a href="/project.cgi?id=$loop1.id" style="color:inherit"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:$loop1.color;margin-right:6px"></span>$loop1.name</a></td> | |
| 185 | <td><span class="badge">$loop1.status</span></td> | |
| 186 | <td><span class="badge">$loop1.priority</span></td> | |
| 187 | <td class="mono"><span class="ts" data-ts="$loop1.target_due_epoch" data-fmt="date">$loop1.target_due_date</span></td> | |
| 188 | </tr> | |
| 189 | [/loop] | |
| 190 | </tbody> | |
| 191 | </table> | |
| 192 | [/if] | |
| 193 | [if:!$has_projects]<div style="color:var(--col-text-dim);font-size:13px;padding:10px 0">No projects owned by this user.</div>[/if] | |
| 194 | </div> | |
| 195 | ||
| 196 | <div class="au-card" style="margin-top:14px"> | |
| 197 | <div class="au-card-title">Assigned tasks · $t_open open</div> | |
| 198 | [if:$has_tasks] | |
| 199 | <table class="au-table"> | |
| 200 | <thead><tr><th>Ticket</th><th>Title</th><th>Status</th><th>Priority</th><th>Due</th></tr></thead> | |
| 201 | <tbody> | |
| 202 | [loop:@tasks] | |
| 203 | <tr> | |
| 204 | <td class="mono"><a href="/task.cgi?id=$loop1.id" style="color:var(--col-primary)">$loop1.ticket</a></td> | |
| 205 | <td>$loop1.title</td> | |
| 206 | <td><span class="badge">$loop1.status</span></td> | |
| 207 | <td><span class="badge">$loop1.priority</span></td> | |
| 208 | <td class="mono"><span class="ts" data-ts="$loop1.due_epoch" data-fmt="date">$loop1.due_date</span></td> | |
| 209 | </tr> | |
| 210 | [/loop] | |
| 211 | </tbody> | |
| 212 | </table> | |
| 213 | [/if] | |
| 214 | [if:!$has_tasks]<div style="color:var(--col-text-dim);font-size:13px;padding:10px 0">No open tasks assigned to this user.</div>[/if] | |
| 215 | </div> | |
| 216 | </div> | |
| 217 | </div> |