UI: Reseller aufklappbar (Admins) + Firmen-Aufklapp nur einloggbare
- ResellersView: Zeilen aufklappbar → Reseller-Admins (login + ROLE_RESELLER_ADMIN) je „Einloggen als"; Plattform-Org ohne Admins bleibt zu. - CompaniesView: Aufklapp listet nur noch einloggbare Mitarbeiter (login=true), „kein Login"-Zeilen entfallen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7af4eafcad
commit
cc92b48869
@ -36,6 +36,10 @@ function slugify(s: string) {
|
||||
function empOf(c: Company) {
|
||||
return employees.value.filter((e) => e.company === c['@id'])
|
||||
}
|
||||
// Nur Mitarbeiter, deren Login man übernehmen kann
|
||||
function loginable(c: Company) {
|
||||
return empOf(c).filter((e) => e.login)
|
||||
}
|
||||
function activeProfiles(c: Company) {
|
||||
return empOf(c).filter((e) => e.status === 'active').length
|
||||
}
|
||||
@ -137,18 +141,16 @@ onMounted(load)
|
||||
<td></td>
|
||||
<td colspan="7">
|
||||
<table class="subtbl">
|
||||
<thead><tr><th>Mitarbeiter</th><th>E-Mail</th><th>Telefon</th><th>Position</th><th></th></tr></thead>
|
||||
<thead><tr><th>Mitarbeiter</th><th>E-Mail</th><th>Position</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-if="!empOf(c).length"><td colspan="5" class="muted sm">Keine Mitarbeiter.</td></tr>
|
||||
<tr v-for="e in empOf(c)" :key="e.id">
|
||||
<tr v-if="!loginable(c).length"><td colspan="4" class="muted sm">Keine einloggbaren Mitarbeiter.</td></tr>
|
||||
<tr v-for="e in loginable(c)" :key="e.id">
|
||||
<td><strong>{{ e.firstName }} {{ e.lastName }}</strong></td>
|
||||
<td class="muted">{{ e.email || '–' }}</td>
|
||||
<td class="muted">{{ e.phone || '–' }}</td>
|
||||
<td>{{ e.position || '–' }}</td>
|
||||
<td class="right">
|
||||
<button v-if="e.login" class="btn btn-ghost btn-sm orange" :disabled="busy !== ''"
|
||||
<button class="btn btn-ghost btn-sm orange" :disabled="busy !== ''"
|
||||
@click="loginAs(e.id)">⤷ Einloggen als</button>
|
||||
<span v-else class="muted sm">kein Login</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -13,7 +13,7 @@ interface Reseller {
|
||||
}
|
||||
interface Plan { '@id': string; id: string; name: string }
|
||||
interface Company { '@id': string; reseller: string | null }
|
||||
interface Employee { '@id': string; id: string; company: string; roles: string[]; login: boolean }
|
||||
interface Employee { '@id': string; id: string; firstName: string; lastName: string; email: string | null; company: string; roles: string[]; login: boolean }
|
||||
|
||||
const auth = useAuthStore()
|
||||
const router = useRouter()
|
||||
@ -24,13 +24,17 @@ const employees = ref<Employee[]>([])
|
||||
const busy = ref('')
|
||||
const loading = ref(true)
|
||||
|
||||
// Firma-IRI → Reseller-IRI, um den Reseller-Admin je Reseller zu finden
|
||||
const expanded = ref<Record<string, boolean>>({})
|
||||
function toggle(r: Reseller) { expanded.value[r.id] = !expanded.value[r.id] }
|
||||
|
||||
// Firma-IRI → Reseller-IRI, um die einloggbaren Reseller-Admins je Reseller zu finden
|
||||
const companyReseller = computed(() => Object.fromEntries(companies.value.map((c) => [c['@id'], c.reseller])))
|
||||
function resellerAdmin(r: Reseller): Employee | undefined {
|
||||
return employees.value.find((e) =>
|
||||
function resellerAdmins(r: Reseller): Employee[] {
|
||||
return employees.value.filter((e) =>
|
||||
e.login && e.roles.includes('ROLE_RESELLER_ADMIN') && companyReseller.value[e.company] === r['@id'],
|
||||
)
|
||||
}
|
||||
function resellerAdmin(r: Reseller): Employee | undefined { return resellerAdmins(r)[0] }
|
||||
async function loginAs(employeeId: string) {
|
||||
busy.value = employeeId
|
||||
try {
|
||||
@ -96,22 +100,40 @@ onMounted(load)
|
||||
|
||||
<div class="card">
|
||||
<table class="tbl">
|
||||
<thead><tr><th>Name</th><th>Domain</th><th>Paket</th><th>Firmen</th><th>Status</th><th></th></tr></thead>
|
||||
<thead><tr><th class="w-caret"></th><th>Name</th><th>Domain</th><th>Paket</th><th>Firmen</th><th>Status</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-if="loading"><td colspan="6" class="empty">Lädt…</td></tr>
|
||||
<tr v-else-if="!resellers.length"><td colspan="6" class="empty">Noch keine Reseller.</td></tr>
|
||||
<tr v-for="r in resellers" :key="r.id">
|
||||
<td><strong>{{ r.name }}</strong><div class="muted small">{{ r.slug }}</div></td>
|
||||
<td class="muted">{{ r.primaryDomain ?? '–' }}</td>
|
||||
<td>{{ r.platformPlan ? (planMap[r.platformPlan] ?? '—') : '—' }}</td>
|
||||
<td>{{ r.companies?.length ?? 0 }}</td>
|
||||
<td><span class="badge" :class="r.status === 'active' ? 'badge-active' : 'badge-inactive'">{{ r.status }}</span></td>
|
||||
<td class="right">
|
||||
<button v-if="resellerAdmin(r)" class="btn btn-soft btn-sm" :disabled="busy !== ''"
|
||||
@click="loginAs(resellerAdmin(r)!.id)">Einloggen als</button>
|
||||
<button class="btn btn-ghost btn-sm" @click="del(r)">Löschen</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="loading"><td colspan="7" class="empty">Lädt…</td></tr>
|
||||
<tr v-else-if="!resellers.length"><td colspan="7" class="empty">Noch keine Reseller.</td></tr>
|
||||
<template v-for="r in resellers" :key="r.id">
|
||||
<tr :class="{ rrow: resellerAdmins(r).length }" @click="resellerAdmins(r).length && toggle(r)">
|
||||
<td class="w-caret"><span v-if="resellerAdmins(r).length" class="caret" :class="{ open: expanded[r.id] }">›</span></td>
|
||||
<td><strong>{{ r.name }}</strong><div class="muted small">{{ r.slug }}</div></td>
|
||||
<td class="muted">{{ r.primaryDomain ?? '–' }}</td>
|
||||
<td>{{ r.platformPlan ? (planMap[r.platformPlan] ?? '—') : '—' }}</td>
|
||||
<td>{{ r.companies?.length ?? 0 }}</td>
|
||||
<td><span class="badge" :class="r.status === 'active' ? 'badge-active' : 'badge-inactive'">{{ r.status }}</span></td>
|
||||
<td class="right" @click.stop>
|
||||
<button v-if="resellerAdmin(r)" class="btn btn-soft btn-sm" :disabled="busy !== ''"
|
||||
@click="loginAs(resellerAdmin(r)!.id)">Einloggen als</button>
|
||||
<button class="btn btn-ghost btn-sm" @click="del(r)">Löschen</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="expanded[r.id]" class="subrow">
|
||||
<td></td>
|
||||
<td colspan="6">
|
||||
<table class="subtbl">
|
||||
<thead><tr><th>Reseller-Admin</th><th>E-Mail</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="a in resellerAdmins(r)" :key="a.id">
|
||||
<td><strong>{{ a.firstName }} {{ a.lastName }}</strong></td>
|
||||
<td class="muted">{{ a.email || '–' }}</td>
|
||||
<td class="right"><button class="btn btn-ghost btn-sm orange" :disabled="busy !== ''" @click="loginAs(a.id)">⤷ Einloggen als</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -153,8 +175,19 @@ onMounted(load)
|
||||
.tbl td { padding: .9rem 1.2rem; border-bottom: 1px solid #f4f4f4; }
|
||||
.tbl tr:last-child td { border-bottom: none; }
|
||||
.small { font-size: .8rem; }
|
||||
.right { text-align: right; }
|
||||
.right { text-align: right; white-space: nowrap; }
|
||||
.right .btn + .btn { margin-left: .4rem; }
|
||||
.empty { text-align: center; color: var(--muted); padding: 2rem; }
|
||||
.w-caret { width: 28px; }
|
||||
.caret { display: inline-block; transition: transform .15s; color: var(--muted); font-size: 1.1rem; }
|
||||
.caret.open { transform: rotate(90deg); }
|
||||
.rrow { cursor: pointer; }
|
||||
.rrow:hover { background: #fafafa; }
|
||||
.orange { color: var(--psc-orange-dark); }
|
||||
.subrow > td { background: #fbfbfb; padding: 0 1.2rem 1rem !important; }
|
||||
.subtbl { width: 100%; border-collapse: collapse; }
|
||||
.subtbl th { text-align: left; font-size: .68rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: .6rem .7rem; }
|
||||
.subtbl td { padding: .55rem .7rem; border-top: 1px solid #f0f0f0; }
|
||||
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
|
||||
.divider { font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; margin: .4rem 0 .8rem; padding-top: .8rem; border-top: 1px solid var(--line); }
|
||||
.actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1rem; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user