feat: expand launcher admin mock controls
This commit is contained in:
parent
db0eabe7d7
commit
85aa322990
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue