init: M1 scaffolding + M2 organization/clients/services CRUD
- monorepo (npm workspaces): apps/api (Fastify+Prisma+TS), apps/web (Vite+React+TS), packages/shared (zod schemas) - SSO via auth.queo.ru: jose+JWKS plugin, requireDocPermission(viewer|user|admin) - DEV_BYPASS_AUTH for local development (hard-checked off in production) - M2: organization upsert, clients CRUD with search, services catalog with soft-delete - BigInt -> Number serializer for Prisma money columns - Embedded Postgres + npm run dev:demo for one-command local boot - Docker compose for queoserver: postgres + api + web (nginx as ingress proxying /api -> api:3030) - First migration 0_init committed (prisma migrate diff) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
:root {
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f6f7f9;
|
||||
color: #1c1f24;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { background: #14161a; color: #e7e8eb; }
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 12px 24px;
|
||||
background: #1c1f24;
|
||||
color: #f6f7f9;
|
||||
border-bottom: 1px solid #2a2e35;
|
||||
}
|
||||
.topbar h1 { margin: 0; font-size: 18px; font-weight: 600; }
|
||||
.topbar nav { display: flex; gap: 16px; flex: 1; }
|
||||
.topbar nav a { color: #c9cbcf; text-decoration: none; }
|
||||
.topbar nav a:hover { color: #fff; }
|
||||
.topbar .user { opacity: 0.7; font-size: 13px; }
|
||||
|
||||
.content { padding: 24px; max-width: 1200px; margin: 0 auto; }
|
||||
|
||||
.loading {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: 100vh; opacity: 0.6;
|
||||
}
|
||||
|
||||
/* === page primitives === */
|
||||
.page-head {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-head h2 { margin: 0; }
|
||||
.toolbar {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.search {
|
||||
flex: 1; max-width: 360px;
|
||||
padding: 8px 12px; border: 1px solid #d6d8dd; border-radius: 6px;
|
||||
background: #fff; color: inherit; font-size: 14px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.search { background: #1c1f24; border-color: #2a2e35; }
|
||||
}
|
||||
.checkbox {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.hint { opacity: 0.65; font-size: 13px; margin: 4px 0; }
|
||||
.error-text { color: #c0392b; font-size: 13px; margin: 8px 0; }
|
||||
.empty {
|
||||
padding: 48px 24px; text-align: center; opacity: 0.6;
|
||||
border: 1px dashed #d6d8dd; border-radius: 8px;
|
||||
}
|
||||
|
||||
/* === buttons === */
|
||||
.btn {
|
||||
appearance: none; cursor: pointer;
|
||||
padding: 6px 14px; border: 1px solid transparent; border-radius: 6px;
|
||||
font-size: 14px; line-height: 1.4;
|
||||
transition: background-color 0.1s, border-color 0.1s;
|
||||
}
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn--default { background: #fff; border-color: #d6d8dd; color: inherit; }
|
||||
.btn--default:hover:not(:disabled) { background: #f1f2f5; }
|
||||
.btn--primary { background: #2563eb; color: #fff; }
|
||||
.btn--primary:hover:not(:disabled) { background: #1d4ed8; }
|
||||
.btn--danger { background: transparent; border-color: #c0392b; color: #c0392b; }
|
||||
.btn--danger:hover:not(:disabled) { background: #c0392b; color: #fff; }
|
||||
.btn--ghost { background: transparent; color: inherit; }
|
||||
.btn--ghost:hover:not(:disabled) { background: rgba(127,127,127,0.1); }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.btn--default { background: #1c1f24; border-color: #2a2e35; }
|
||||
.btn--default:hover:not(:disabled) { background: #25282e; }
|
||||
}
|
||||
|
||||
/* === tables === */
|
||||
.table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
background: #fff; border-radius: 8px; overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
.table th, .table td {
|
||||
text-align: left; padding: 10px 14px; vertical-align: top;
|
||||
border-bottom: 1px solid #eef0f3;
|
||||
font-size: 14px;
|
||||
}
|
||||
.table th { font-weight: 600; opacity: 0.7; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.row-actions { white-space: nowrap; text-align: right; }
|
||||
.row-actions .btn { margin-left: 6px; }
|
||||
.row--archived { opacity: 0.55; }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.table { background: #1c1f24; box-shadow: none; }
|
||||
.table th, .table td { border-bottom-color: #2a2e35; }
|
||||
}
|
||||
|
||||
/* === fields === */
|
||||
.field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.field__label { font-size: 12px; opacity: 0.7; }
|
||||
.field__input {
|
||||
padding: 8px 10px; border: 1px solid #d6d8dd; border-radius: 6px;
|
||||
background: #fff; color: inherit; font-size: 14px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.field__input--area { min-height: 64px; resize: vertical; }
|
||||
.field__input--err { border-color: #c0392b; }
|
||||
.field__error { color: #c0392b; font-size: 12px; }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.field__input { background: #1c1f24; border-color: #2a2e35; }
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;
|
||||
}
|
||||
.form-actions { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
|
||||
|
||||
/* === modal === */
|
||||
.modal__backdrop {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 100; padding: 24px;
|
||||
}
|
||||
.modal {
|
||||
background: #fff; color: inherit;
|
||||
border-radius: 8px; width: min(720px, 100%);
|
||||
max-height: calc(100vh - 48px); overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
}
|
||||
.modal__header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 20px; border-bottom: 1px solid #eef0f3;
|
||||
}
|
||||
.modal__header h3 { margin: 0; font-size: 16px; }
|
||||
.modal__close {
|
||||
background: none; border: none; color: inherit; cursor: pointer;
|
||||
font-size: 22px; line-height: 1; padding: 4px 8px; border-radius: 4px;
|
||||
}
|
||||
.modal__close:hover { background: rgba(127,127,127,0.1); }
|
||||
.modal__body { padding: 20px; overflow: auto; }
|
||||
.modal__footer {
|
||||
display: flex; justify-content: flex-end; gap: 8px;
|
||||
padding: 12px 20px; border-top: 1px solid #eef0f3;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.modal { background: #1c1f24; }
|
||||
.modal__header, .modal__footer { border-color: #2a2e35; }
|
||||
}
|
||||
Reference in New Issue
Block a user