feat: expand launcher admin mock controls

This commit is contained in:
DCCONSTRUCTIONS 2026-05-01 21:56:58 +03:00
parent db0eabe7d7
commit 85aa322990
6 changed files with 1397 additions and 154 deletions

View File

@ -3,15 +3,15 @@
{
"id": "client_romashka",
"type": "company",
"name": "ООО Ромашка",
"legalName": "ООО Ромашка",
"name": "DCTOUCH",
"legalName": "ООО ДИСИТАЧ",
"status": "active",
"demoEndsAt": null,
"contactName": "Иван Петров",
"contactEmail": "ivan@romashka.ru",
"contactEmail": "suppert@dctouch.ru",
"notes": "Основной demo-клиент для проверки Task Manager, NodeDC и deny-исключений.",
"createdAt": "2026-04-01T10:00:00Z",
"updatedAt": "2026-05-01T09:00:00Z"
"updatedAt": "2026-05-01T18:49:19.215Z"
},
{
"id": "client_roga_kopyta",
@ -102,7 +102,7 @@
"email": "oleg@romashka.ru",
"globalStatus": "blocked",
"createdAt": "2026-04-12T10:00:00Z",
"updatedAt": "2026-05-01T09:00:00Z"
"updatedAt": "2026-05-01T18:49:59.865Z"
}
],
"memberships": [

View File

@ -1,8 +1,10 @@
import { useEffect, useMemo, useState } from "react";
import type { ServiceAccessException, ServiceGrant } from "../entities/access/types";
import type { Client } from "../entities/client/types";
import type { Invite } from "../entities/invite/types";
import type { LauncherServiceView, Service } from "../entities/service/types";
import type { SyncStatus } from "../entities/sync/types";
import type { ClientGroup, ClientMembership, LauncherUser } from "../entities/user/types";
import {
buildLauncherServices,
buildMe,
@ -169,6 +171,21 @@ export function LauncherApp() {
}));
}
function handleUpdateInvite(inviteId: string, patch: Partial<Invite>) {
setData((current) => ({
...current,
invites: current.invites.map((invite) =>
invite.id === inviteId
? {
...invite,
...patch,
updatedAt: new Date().toISOString(),
}
: invite
),
}));
}
function handleRetrySync(syncId: string) {
setData((current) => ({
...current,
@ -200,6 +217,111 @@ export function LauncherApp() {
}));
}
function handleCreateClient() {
const createdAt = new Date().toISOString();
const index = data.clients.length + 1;
setData((current) => ({
...current,
clients: [
...current.clients,
{
id: `client_mock_${Date.now()}`,
type: "company",
name: `Новый клиент ${index}`,
legalName: `Новый клиент ${index}`,
status: "demo",
demoEndsAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
contactName: "",
contactEmail: "",
notes: "",
createdAt,
updatedAt: createdAt,
},
],
}));
}
function handleUpdateClient(clientId: string, patch: Partial<Client>) {
setData((current) => ({
...current,
clients: current.clients.map((client) =>
client.id === clientId
? {
...client,
...patch,
updatedAt: new Date().toISOString(),
}
: client
),
}));
}
function handleUpdateUser(userId: string, patch: Partial<LauncherUser>) {
setData((current) => ({
...current,
users: current.users.map((user) =>
user.id === userId
? {
...user,
...patch,
updatedAt: new Date().toISOString(),
}
: user
),
}));
}
function handleUpdateMembership(membershipId: string, patch: Partial<ClientMembership>) {
setData((current) => ({
...current,
memberships: current.memberships.map((membership) =>
membership.id === membershipId
? {
...membership,
...patch,
updatedAt: new Date().toISOString(),
}
: membership
),
}));
}
function handleCreateGroup(clientId: string) {
const createdAt = new Date().toISOString();
setData((current) => ({
...current,
groups: [
...current.groups,
{
id: `group_mock_${Date.now()}`,
clientId,
name: "Новая группа",
description: "Описание группы",
memberIds: [],
createdAt,
updatedAt: createdAt,
},
],
}));
}
function handleUpdateGroup(groupId: string, patch: Partial<ClientGroup>) {
setData((current) => ({
...current,
groups: current.groups.map((group) =>
group.id === groupId
? {
...group,
...patch,
updatedAt: new Date().toISOString(),
}
: group
),
}));
}
function handleReorderServices(orderedServiceIds: string[]) {
setData((current) => {
const orderById = new Map(orderedServiceIds.map((serviceId, index) => [serviceId, (index + 1) * 10]));
@ -304,7 +426,14 @@ export function LauncherApp() {
onCreateDenyException={handleCreateDenyException}
onRemoveException={handleRemoveException}
onCreateInvite={handleCreateInvite}
onUpdateInvite={handleUpdateInvite}
onRetrySync={handleRetrySync}
onCreateClient={handleCreateClient}
onUpdateClient={handleUpdateClient}
onUpdateUser={handleUpdateUser}
onUpdateMembership={handleUpdateMembership}
onCreateGroup={handleCreateGroup}
onUpdateGroup={handleUpdateGroup}
onUpdateService={handleUpdateService}
onReorderServices={handleReorderServices}
onCreateService={handleCreateService}

View File

@ -6,7 +6,11 @@ export interface Client {
type: ClientType;
name: string;
legalName?: string | null;
inn?: string | null;
status: ClientStatus;
contractStartsAt?: string | null;
contractEndsAt?: string | null;
paidUntil?: string | null;
demoEndsAt?: string | null;
contactName?: string | null;
contactEmail?: string | null;

View File

@ -12,6 +12,9 @@ export interface LauncherUser {
authentikUserId?: string | null;
email: string;
name: string;
phone?: string | null;
position?: string | null;
notes?: string | null;
avatarUrl?: string | null;
globalStatus: LauncherUserStatus;
createdAt: string;

View File

@ -1392,7 +1392,7 @@ code {
}
.admin-panel-content .admin-section-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.admin-panel-content .access-layout,
@ -1595,6 +1595,12 @@ code {
color: rgb(var(--nodedc-on-accent-rgb));
}
.admin-circle-action:disabled {
cursor: default;
opacity: 0.36;
filter: grayscale(1);
}
.admin-section-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
@ -1776,6 +1782,12 @@ code {
font-size: 0.72rem;
}
.admin-table-input--select {
appearance: none;
background: rgba(255, 255, 255, 0.045);
cursor: pointer;
}
.service-status-dropdown {
width: 7.45rem;
min-width: 7.45rem;
@ -1912,6 +1924,218 @@ code {
background: rgba(255, 120, 120, 0.32);
}
.admin-status-dropdown {
width: 8.65rem;
min-width: 8.65rem;
}
.admin-status-trigger {
display: inline-flex;
width: 8.65rem;
min-height: 2.08rem;
align-items: center;
justify-content: center;
border: 0;
border-radius: var(--launcher-radius-circle);
background: rgba(255, 255, 255, 0.075);
color: rgba(255, 255, 255, 0.84);
padding: 0 0.7rem;
font-size: 0.72rem;
font-weight: 850;
white-space: nowrap;
}
.admin-status-trigger span {
width: 100%;
text-align: center;
}
.admin-status-trigger[data-tone="green"] {
background: rgba(181, 255, 90, 0.075);
color: rgba(226, 255, 190, 0.94);
}
.admin-status-trigger[data-tone="yellow"] {
background: rgba(246, 201, 95, 0.075);
color: rgba(255, 232, 178, 0.94);
}
.admin-status-trigger[data-tone="violet"] {
background: rgba(210, 197, 255, 0.07);
color: rgba(232, 225, 255, 0.92);
}
.admin-status-trigger[data-tone="red"] {
background: rgba(255, 120, 120, 0.07);
color: rgba(255, 216, 216, 0.92);
}
.admin-status-trigger[data-tone="muted"] {
background: rgba(255, 255, 255, 0.075);
color: rgba(255, 255, 255, 0.78);
}
.admin-status-trigger:hover,
.admin-status-trigger[aria-expanded="true"] {
filter: brightness(1.12);
color: var(--text-primary);
}
.admin-status-trigger--static {
pointer-events: none;
}
.admin-status-menu {
display: grid;
gap: 0.18rem;
min-width: 10.25rem;
}
.admin-status-menu__option {
display: grid;
grid-template-columns: 1rem minmax(0, 1fr);
min-height: 2.45rem;
align-items: center;
border: 0;
border-radius: 0.92rem;
background: transparent;
color: rgba(255, 255, 255, 0.68);
padding: 0 0.78rem;
text-align: left;
font-size: 0.8rem;
font-weight: 780;
}
.admin-status-menu__option span {
text-align: left;
}
.admin-status-menu__mark {
display: block;
width: 0.45rem;
height: 0.45rem;
border-radius: var(--launcher-radius-circle);
background: transparent;
}
.admin-status-menu__option:hover,
.admin-status-menu__option:focus-visible {
background: rgba(255, 255, 255, 0.075);
color: var(--text-primary);
outline: none;
}
.admin-status-menu__option[data-selected="true"] {
background: rgba(255, 255, 255, 0.11);
color: var(--text-primary);
}
.admin-status-menu__option[data-selected="true"] .admin-status-menu__mark {
background: rgba(247, 248, 244, 0.96);
}
.admin-status-menu__option[data-tone="green"][data-selected="true"] {
background: rgba(181, 255, 90, 0.065);
color: rgba(226, 255, 190, 0.96);
}
.admin-status-menu__option[data-tone="yellow"][data-selected="true"] {
background: rgba(246, 201, 95, 0.065);
color: rgba(255, 232, 178, 0.96);
}
.admin-status-menu__option[data-tone="violet"][data-selected="true"] {
background: rgba(210, 197, 255, 0.06);
color: rgba(232, 225, 255, 0.94);
}
.admin-status-menu__option[data-tone="red"][data-selected="true"] {
background: rgba(255, 120, 120, 0.06);
color: rgba(255, 216, 216, 0.94);
}
.admin-status-menu__option[data-tone="green"][data-selected="true"] .admin-status-menu__mark {
background: rgba(181, 255, 90, 0.34);
}
.admin-status-menu__option[data-tone="yellow"][data-selected="true"] .admin-status-menu__mark {
background: rgba(246, 201, 95, 0.34);
}
.admin-status-menu__option[data-tone="violet"][data-selected="true"] .admin-status-menu__mark {
background: rgba(210, 197, 255, 0.32);
}
.admin-status-menu__option[data-tone="red"][data-selected="true"] .admin-status-menu__mark {
background: rgba(255, 120, 120, 0.32);
}
.admin-date-field {
width: 100%;
}
.admin-date-trigger {
display: inline-flex;
width: 100%;
min-height: 2.08rem;
align-items: center;
justify-content: flex-start;
gap: 0.42rem;
border: 0;
border-radius: var(--launcher-radius-circle);
background: rgba(255, 255, 255, 0.052);
color: var(--text-secondary);
padding: 0 0.7rem;
font-size: 0.74rem;
font-weight: 760;
}
.admin-date-trigger:hover,
.admin-date-trigger:focus-visible {
background: rgba(255, 255, 255, 0.085);
color: var(--text-primary);
outline: none;
}
.admin-date-popover {
display: grid;
gap: 0.65rem;
min-width: 14rem;
}
.admin-date-popover input {
min-height: 2.65rem;
border: 0;
border-radius: 0.92rem;
background: rgba(255, 255, 255, 0.08);
color: var(--text-primary);
padding: 0 0.78rem;
font: inherit;
color-scheme: dark;
}
.admin-date-popover__actions {
display: flex;
justify-content: space-between;
gap: 0.45rem;
}
.admin-date-popover__actions button {
min-height: 2.25rem;
border: 0;
border-radius: var(--launcher-radius-circle);
background: rgba(255, 255, 255, 0.08);
color: var(--text-secondary);
padding: 0 0.75rem;
font-size: 0.76rem;
font-weight: 780;
}
.admin-date-popover__actions button:hover {
background: rgba(255, 255, 255, 0.12);
color: var(--text-primary);
}
.admin-table-select {
appearance: none;
border-radius: 999px;
@ -2036,7 +2260,8 @@ code {
}
.service-content-field input,
.service-content-field textarea {
.service-content-field textarea,
.service-content-field select {
width: 100%;
border: 0;
border-radius: 1rem;
@ -2046,6 +2271,11 @@ code {
font-size: 0.84rem;
}
.service-content-field select {
min-height: 2.75rem;
appearance: none;
}
.service-media-field {
min-width: 0;
}
@ -2188,6 +2418,38 @@ code {
color: var(--text-primary);
}
.admin-token-grid {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
min-height: 3rem;
align-items: flex-start;
border-radius: 1rem;
background: rgba(255, 255, 255, 0.045);
padding: 0.48rem;
}
.admin-token {
min-height: 2.1rem;
border: 0;
border-radius: var(--launcher-radius-circle);
background: rgba(255, 255, 255, 0.07);
color: var(--text-secondary);
padding: 0 0.8rem;
font-size: 0.76rem;
font-weight: 780;
}
.admin-token:hover {
background: rgba(255, 255, 255, 0.11);
color: var(--text-primary);
}
.admin-token[data-active="true"] {
background: rgba(247, 248, 244, 0.96);
color: rgb(var(--nodedc-on-accent-rgb));
}
.access-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 21rem;
@ -2276,12 +2538,26 @@ code {
overflow: hidden;
}
.invites-layout--catalog {
grid-template-columns: 1fr;
}
.invite-form {
display: grid;
align-content: start;
gap: 0.75rem;
}
.invite-form--compact {
padding: 1rem;
}
.invite-form__fields {
display: grid;
grid-template-columns: minmax(0, 1fr) 12rem;
gap: 0.65rem;
}
.invite-form input,
.invite-form select {
min-height: 2.7rem;
@ -2292,6 +2568,33 @@ code {
padding: 0 0.8rem;
}
.invite-form select {
appearance: none;
}
.invite-link-cell {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 0.45rem;
}
.invite-link-cell code {
min-width: 0;
overflow: hidden;
color: var(--text-secondary);
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-helper-note {
max-width: 38rem;
margin: 0.22rem 0 0;
color: var(--text-muted);
font-size: 0.73rem;
line-height: 1.35;
}
.company-panel {
max-width: 42rem;
}

File diff suppressed because it is too large Load Diff