From 11eabe42ae680423d8b8473ab7fc4ab2bb1e24bf Mon Sep 17 00:00:00 2001 From: Erik Dunin Date: Thu, 1 Oct 2026 10:54:43 +0200 Subject: [PATCH] Protokoll-Import: KI-Erkennung (Gruppe, Teilnehmer, Status, Mehrfachpunkte), Ladeanzeige, Erstellungsdatum je Punkt Co-Authored-By: Claude Sonnet 5 --- .gitignore | 1 + CLAUDE.md | 2805 ++++++++- README.md | 26 +- node_modules/.DS_Store | Bin 12292 -> 12292 bytes node_modules/.package-lock.json | 9 + node_modules/undici/LICENSE | 21 + node_modules/undici/README.md | 806 +++ node_modules/undici/docs/docs/api/Agent.md | 291 + .../undici/docs/docs/api/BalancedPool.md | 297 + .../undici/docs/docs/api/CacheStorage.md | 188 + .../undici/docs/docs/api/CacheStore.md | 360 ++ node_modules/undici/docs/docs/api/Client.md | 533 ++ .../undici/docs/docs/api/ClientStats.md | 93 + .../undici/docs/docs/api/Connector.md | 198 + .../undici/docs/docs/api/ContentType.md | 97 + node_modules/undici/docs/docs/api/Cookies.md | 179 + node_modules/undici/docs/docs/api/Debug.md | 78 + .../docs/docs/api/DiagnosticsChannel.md | 518 ++ .../undici/docs/docs/api/Dispatcher.md | 836 +++ .../undici/docs/docs/api/EnvHttpProxyAgent.md | 171 + node_modules/undici/docs/docs/api/Errors.md | 549 ++ .../undici/docs/docs/api/EventSource.md | 358 ++ node_modules/undici/docs/docs/api/Fetch.md | 721 +++ .../docs/docs/api/GlobalInstallation.md | 121 + .../undici/docs/docs/api/H2CClient.md | 385 ++ .../undici/docs/docs/api/Interceptors.md | 419 ++ .../undici/docs/docs/api/MockAgent.md | 622 ++ .../undici/docs/docs/api/MockCallHistory.md | 371 ++ .../docs/docs/api/MockCallHistoryLog.md | 206 + .../undici/docs/docs/api/MockClient.md | 199 + .../undici/docs/docs/api/MockErrors.md | 63 + node_modules/undici/docs/docs/api/MockPool.md | 618 ++ node_modules/undici/docs/docs/api/Pool.md | 278 + .../undici/docs/docs/api/PoolStats.md | 141 + .../undici/docs/docs/api/ProxyAgent.md | 248 + .../undici/docs/docs/api/RedirectHandler.md | 276 + .../undici/docs/docs/api/RetryAgent.md | 136 + .../undici/docs/docs/api/RetryHandler.md | 200 + .../undici/docs/docs/api/RoundRobinPool.md | 239 + .../undici/docs/docs/api/SnapshotAgent.md | 361 ++ .../undici/docs/docs/api/Socks5ProxyAgent.md | 256 + node_modules/undici/docs/docs/api/Util.md | 82 + .../undici/docs/docs/api/WebSocket.md | 536 ++ .../undici/docs/docs/api/api-lifecycle.md | 159 + .../docs/best-practices/client-certificate.md | 66 + .../docs/docs/best-practices/crawling.md | 60 + .../best-practices/migrating-from-v7-to-v8.md | 233 + .../docs/best-practices/mocking-request.md | 192 + .../undici/docs/docs/best-practices/proxy.md | 129 + .../best-practices/undici-vs-builtin-fetch.md | 226 + .../docs/docs/best-practices/writing-tests.md | 65 + .../undici/docs/docs/getting-started.md | 281 + node_modules/undici/docs/docs/index.md | 793 +++ node_modules/undici/docs/docs/site.json | 125 + node_modules/undici/docs/docs/type-map.json | 79 + node_modules/undici/index-fetch.js | 65 + node_modules/undici/index.d.ts | 3 + node_modules/undici/index.js | 236 + node_modules/undici/lib/api/abort-signal.js | 59 + node_modules/undici/lib/api/api-connect.js | 112 + node_modules/undici/lib/api/api-pipeline.js | 265 + node_modules/undici/lib/api/api-request.js | 244 + node_modules/undici/lib/api/api-stream.js | 270 + node_modules/undici/lib/api/api-upgrade.js | 121 + node_modules/undici/lib/api/index.js | 7 + node_modules/undici/lib/api/readable.js | 615 ++ .../undici/lib/cache/memory-cache-store.js | 279 + .../undici/lib/cache/sqlite-cache-store.js | 469 ++ node_modules/undici/lib/core/connect.js | 192 + node_modules/undici/lib/core/constants.js | 120 + node_modules/undici/lib/core/diagnostics.js | 227 + node_modules/undici/lib/core/errors.js | 497 ++ node_modules/undici/lib/core/request.js | 567 ++ node_modules/undici/lib/core/socks5-client.js | 422 ++ node_modules/undici/lib/core/socks5-utils.js | 212 + node_modules/undici/lib/core/symbols.js | 79 + node_modules/undici/lib/core/tree.js | 160 + node_modules/undici/lib/core/util.js | 1010 ++++ node_modules/undici/lib/dispatcher/agent.js | 177 + .../undici/lib/dispatcher/balanced-pool.js | 217 + .../undici/lib/dispatcher/client-h1.js | 1808 ++++++ .../undici/lib/dispatcher/client-h2.js | 1825 ++++++ node_modules/undici/lib/dispatcher/client.js | 746 +++ .../undici/lib/dispatcher/dispatcher-base.js | 197 + .../undici/lib/dispatcher/dispatcher.js | 58 + .../lib/dispatcher/dispatcher1-wrapper.js | 110 + .../lib/dispatcher/env-http-proxy-agent.js | 190 + .../undici/lib/dispatcher/fixed-queue.js | 135 + .../undici/lib/dispatcher/h2c-client.js | 51 + .../undici/lib/dispatcher/pool-base.js | 293 + node_modules/undici/lib/dispatcher/pool.js | 226 + .../undici/lib/dispatcher/proxy-agent.js | 408 ++ .../undici/lib/dispatcher/retry-agent.js | 38 + .../undici/lib/dispatcher/round-robin-pool.js | 159 + .../lib/dispatcher/socks5-proxy-agent.js | 350 ++ node_modules/undici/lib/encoding/index.js | 33 + node_modules/undici/lib/global.js | 82 + .../undici/lib/handler/cache-handler.js | 818 +++ .../lib/handler/cache-revalidation-handler.js | 134 + .../undici/lib/handler/decorator-handler.js | 72 + .../lib/handler/deduplication-handler.js | 479 ++ .../undici/lib/handler/redirect-handler.js | 243 + .../undici/lib/handler/retry-handler.js | 653 +++ node_modules/undici/lib/interceptor/cache.js | 674 +++ .../undici/lib/interceptor/decompress.js | 561 ++ .../undici/lib/interceptor/deduplicate.js | 118 + node_modules/undici/lib/interceptor/dns.js | 582 ++ node_modules/undici/lib/interceptor/dump.js | 99 + .../undici/lib/interceptor/redirect.js | 21 + .../undici/lib/interceptor/response-error.js | 95 + node_modules/undici/lib/interceptor/retry.js | 19 + node_modules/undici/lib/llhttp/constants.d.ts | 195 + node_modules/undici/lib/llhttp/constants.js | 531 ++ node_modules/undici/lib/llhttp/llhttp-wasm.js | 15 + .../undici/lib/llhttp/llhttp_simd-wasm.js | 15 + node_modules/undici/lib/llhttp/utils.d.ts | 2 + node_modules/undici/lib/llhttp/utils.js | 12 + node_modules/undici/lib/mock/mock-agent.js | 244 + .../undici/lib/mock/mock-call-history.js | 248 + node_modules/undici/lib/mock/mock-client.js | 68 + node_modules/undici/lib/mock/mock-errors.js | 29 + .../undici/lib/mock/mock-interceptor.js | 227 + node_modules/undici/lib/mock/mock-pool.js | 68 + node_modules/undici/lib/mock/mock-symbols.js | 32 + node_modules/undici/lib/mock/mock-utils.js | 720 +++ .../mock/pending-interceptors-formatter.js | 43 + .../undici/lib/mock/snapshot-agent.js | 371 ++ .../undici/lib/mock/snapshot-recorder.js | 623 ++ .../undici/lib/mock/snapshot-utils.js | 158 + node_modules/undici/lib/util/cache.js | 758 +++ node_modules/undici/lib/util/date.js | 670 +++ .../undici/lib/util/runtime-features.js | 93 + node_modules/undici/lib/util/stats.js | 32 + node_modules/undici/lib/util/timers.js | 425 ++ node_modules/undici/lib/web/cache/cache.js | 871 +++ .../undici/lib/web/cache/cachestorage.js | 158 + node_modules/undici/lib/web/cache/util.js | 45 + .../undici/lib/web/cookies/constants.js | 12 + node_modules/undici/lib/web/cookies/index.js | 211 + node_modules/undici/lib/web/cookies/parse.js | 318 ++ node_modules/undici/lib/web/cookies/util.js | 354 ++ .../lib/web/eventsource/eventsource-stream.js | 521 ++ .../undici/lib/web/eventsource/eventsource.js | 524 ++ .../undici/lib/web/eventsource/util.js | 60 + node_modules/undici/lib/web/fetch/LICENSE | 21 + node_modules/undici/lib/web/fetch/body.js | 554 ++ .../undici/lib/web/fetch/constants.js | 131 + node_modules/undici/lib/web/fetch/data-url.js | 596 ++ .../undici/lib/web/fetch/formdata-parser.js | 586 ++ node_modules/undici/lib/web/fetch/formdata.js | 272 + node_modules/undici/lib/web/fetch/global.js | 40 + node_modules/undici/lib/web/fetch/headers.js | 722 +++ node_modules/undici/lib/web/fetch/index.js | 2453 ++++++++ node_modules/undici/lib/web/fetch/request.js | 1139 ++++ node_modules/undici/lib/web/fetch/response.js | 638 +++ node_modules/undici/lib/web/fetch/util.js | 1530 +++++ node_modules/undici/lib/web/infra/index.js | 244 + .../lib/web/subresource-integrity/Readme.md | 9 + .../subresource-integrity.js | 307 + node_modules/undici/lib/web/webidl/index.js | 992 ++++ .../undici/lib/web/websocket/connection.js | 350 ++ .../undici/lib/web/websocket/constants.js | 126 + .../undici/lib/web/websocket/events.js | 348 ++ .../undici/lib/web/websocket/frame.js | 127 + .../lib/web/websocket/permessage-deflate.js | 105 + .../undici/lib/web/websocket/receiver.js | 509 ++ .../undici/lib/web/websocket/sender.js | 109 + .../web/websocket/stream/websocketerror.js | 107 + .../web/websocket/stream/websocketstream.js | 511 ++ node_modules/undici/lib/web/websocket/util.js | 347 ++ .../undici/lib/web/websocket/websocket.js | 783 +++ node_modules/undici/package.json | 152 + node_modules/undici/scripts/strip-comments.js | 10 + node_modules/undici/types/README.md | 6 + node_modules/undici/types/agent.d.ts | 30 + node_modules/undici/types/api.d.ts | 43 + node_modules/undici/types/balanced-pool.d.ts | 30 + .../undici/types/cache-interceptor.d.ts | 179 + node_modules/undici/types/cache.d.ts | 36 + node_modules/undici/types/client-stats.d.ts | 15 + node_modules/undici/types/client.d.ts | 184 + node_modules/undici/types/connector.d.ts | 37 + node_modules/undici/types/content-type.d.ts | 21 + node_modules/undici/types/cookies.d.ts | 30 + .../undici/types/diagnostics-channel.d.ts | 75 + node_modules/undici/types/dispatcher.d.ts | 253 + .../undici/types/dispatcher1-wrapper.d.ts | 7 + .../undici/types/env-http-proxy-agent.d.ts | 22 + node_modules/undici/types/errors.d.ts | 187 + node_modules/undici/types/eventsource.d.ts | 66 + node_modules/undici/types/fetch.d.ts | 235 + node_modules/undici/types/formdata.d.ts | 108 + .../undici/types/global-dispatcher.d.ts | 9 + node_modules/undici/types/global-origin.d.ts | 7 + node_modules/undici/types/h2c-client.d.ts | 73 + node_modules/undici/types/handlers.d.ts | 16 + node_modules/undici/types/header.d.ts | 165 + node_modules/undici/types/index.d.ts | 93 + node_modules/undici/types/interceptors.d.ts | 82 + node_modules/undici/types/mock-agent.d.ts | 69 + .../undici/types/mock-call-history.d.ts | 111 + node_modules/undici/types/mock-client.d.ts | 27 + node_modules/undici/types/mock-errors.d.ts | 12 + .../undici/types/mock-interceptor.d.ts | 98 + node_modules/undici/types/mock-pool.d.ts | 27 + node_modules/undici/types/patch.d.ts | 29 + node_modules/undici/types/pool-stats.d.ts | 19 + node_modules/undici/types/pool.d.ts | 39 + node_modules/undici/types/proxy-agent.d.ts | 36 + node_modules/undici/types/readable.d.ts | 68 + node_modules/undici/types/retry-agent.d.ts | 8 + node_modules/undici/types/retry-handler.d.ts | 125 + .../undici/types/round-robin-pool.d.ts | 39 + node_modules/undici/types/snapshot-agent.d.ts | 113 + .../undici/types/socks5-proxy-agent.d.ts | 27 + node_modules/undici/types/util.d.ts | 18 + node_modules/undici/types/utility.d.ts | 7 + node_modules/undici/types/webidl.d.ts | 352 ++ node_modules/undici/types/websocket.d.ts | 188 + package-lock.json | 12 +- package.json | 3 +- public/Test.html | 1915 ++++++- public/js/board/board.js | 847 ++- public/js/csv.js | 42 - public/js/gantt.js | 129 +- public/js/import.js | 47 +- public/js/pdf.js | 16 +- public/js/people/people.js | 439 +- public/js/plan/plan-drag.js | 120 +- public/js/plan/plan-protokoll.js | 29 +- public/js/plan/plan-state.js | 428 +- public/js/projects/projects.js | 131 +- public/js/server.js | 49 +- public/js/store.js | 293 +- public/js/tabs-auth.js | 142 +- public/js/weeks.js | 217 +- public/plan-styles.css | 10 +- public/project-plan.html | 330 -- public/styles.css | 3656 ++++++++++-- server.js | 5032 +++++++++++++++-- ...ojektuebersicht_2026-08-18T06-36-48-511.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T06-41-14-950.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T06-41-14-963.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T06-43-13-525.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T06-47-01-551.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T07-09-35-889.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T07-10-17-549.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T07-10-17-555.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T07-12-27-276.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-08-18T07-14-16-193.db | Bin 212992 -> 0 bytes ...ojektuebersicht_2026-09-30T16-34-30-986.db | Bin 421888 -> 0 bytes ...ojektuebersicht_2026-09-30T16-54-16-040.db | Bin 487424 -> 0 bytes ...ojektuebersicht_2026-09-30T17-34-47-236.db | Bin 430080 -> 0 bytes ...ojektuebersicht_2026-09-30T19-53-04-481.db | Bin 430080 -> 0 bytes ...ojektuebersicht_2026-09-30T20-01-56-518.db | Bin 430080 -> 0 bytes ...jektuebersicht_2026-10-01T05-17-34-155.db} | Bin 430080 -> 430080 bytes ...ojektuebersicht_2026-10-01T05-18-05-602.db | Bin 0 -> 430080 bytes ...jektuebersicht_2026-10-01T05-40-23-486.db} | Bin 430080 -> 430080 bytes ...jektuebersicht_2026-10-01T05-57-45-880.db} | Bin 430080 -> 430080 bytes ...jektuebersicht_2026-10-01T06-11-14-181.db} | Bin 430080 -> 430080 bytes ...ojektuebersicht_2026-10-01T06-20-46-331.db | Bin 0 -> 495616 bytes ...ojektuebersicht_2026-10-01T06-39-56-328.db | Bin 0 -> 495616 bytes ...jektuebersicht_2026-10-01T07-34-02-333.db} | Bin 430080 -> 479232 bytes ...ojektuebersicht_2026-10-01T07-37-46-981.db | Bin 0 -> 479232 bytes ...ojektuebersicht_2026-10-01T08-18-09-891.db | Bin 0 -> 479232 bytes .../234e470b-44c8-4b87-9061-05d1b5a971cb.pdf | Bin .../36eeafdc-9e52-4204-b6cf-c98039131f89.pdf | Bin 0 -> 688067 bytes .../3ed65781-10ed-43c3-9c0a-f159ed1fdb6f.pdf | Bin 0 -> 688067 bytes .../6636ee95-c8fa-4c67-867a-b59cd00e8e25.pdf | Bin 0 -> 688067 bytes .../780bee5e-4cb4-46db-86d6-85640de9a5bb.pdf | Bin 0 -> 688067 bytes .../80240a19-b9cc-41dd-9f65-ab4340bfa4c5.pdf | Bin 0 -> 688067 bytes .../a5e2c3ea-d0d9-4cbd-8983-74e1cbaadc66.pdf | Bin 0 -> 688067 bytes .../e1171869-9c24-4f8c-a682-a0f91cc39ab4.pdf | Bin 0 -> 688067 bytes .../e81cf06e-e07a-4075-a268-ecafb29e118d.pdf | Bin 0 -> 688067 bytes .../ef310408-d7a2-44a2-ac86-79a9a774b44a.pdf | Bin 0 -> 688067 bytes 275 files changed, 71952 insertions(+), 3029 deletions(-) create mode 100644 node_modules/undici/LICENSE create mode 100644 node_modules/undici/README.md create mode 100644 node_modules/undici/docs/docs/api/Agent.md create mode 100644 node_modules/undici/docs/docs/api/BalancedPool.md create mode 100644 node_modules/undici/docs/docs/api/CacheStorage.md create mode 100644 node_modules/undici/docs/docs/api/CacheStore.md create mode 100644 node_modules/undici/docs/docs/api/Client.md create mode 100644 node_modules/undici/docs/docs/api/ClientStats.md create mode 100644 node_modules/undici/docs/docs/api/Connector.md create mode 100644 node_modules/undici/docs/docs/api/ContentType.md create mode 100644 node_modules/undici/docs/docs/api/Cookies.md create mode 100644 node_modules/undici/docs/docs/api/Debug.md create mode 100644 node_modules/undici/docs/docs/api/DiagnosticsChannel.md create mode 100644 node_modules/undici/docs/docs/api/Dispatcher.md create mode 100644 node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md create mode 100644 node_modules/undici/docs/docs/api/Errors.md create mode 100644 node_modules/undici/docs/docs/api/EventSource.md create mode 100644 node_modules/undici/docs/docs/api/Fetch.md create mode 100644 node_modules/undici/docs/docs/api/GlobalInstallation.md create mode 100644 node_modules/undici/docs/docs/api/H2CClient.md create mode 100644 node_modules/undici/docs/docs/api/Interceptors.md create mode 100644 node_modules/undici/docs/docs/api/MockAgent.md create mode 100644 node_modules/undici/docs/docs/api/MockCallHistory.md create mode 100644 node_modules/undici/docs/docs/api/MockCallHistoryLog.md create mode 100644 node_modules/undici/docs/docs/api/MockClient.md create mode 100644 node_modules/undici/docs/docs/api/MockErrors.md create mode 100644 node_modules/undici/docs/docs/api/MockPool.md create mode 100644 node_modules/undici/docs/docs/api/Pool.md create mode 100644 node_modules/undici/docs/docs/api/PoolStats.md create mode 100644 node_modules/undici/docs/docs/api/ProxyAgent.md create mode 100644 node_modules/undici/docs/docs/api/RedirectHandler.md create mode 100644 node_modules/undici/docs/docs/api/RetryAgent.md create mode 100644 node_modules/undici/docs/docs/api/RetryHandler.md create mode 100644 node_modules/undici/docs/docs/api/RoundRobinPool.md create mode 100644 node_modules/undici/docs/docs/api/SnapshotAgent.md create mode 100644 node_modules/undici/docs/docs/api/Socks5ProxyAgent.md create mode 100644 node_modules/undici/docs/docs/api/Util.md create mode 100644 node_modules/undici/docs/docs/api/WebSocket.md create mode 100644 node_modules/undici/docs/docs/api/api-lifecycle.md create mode 100644 node_modules/undici/docs/docs/best-practices/client-certificate.md create mode 100644 node_modules/undici/docs/docs/best-practices/crawling.md create mode 100644 node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md create mode 100644 node_modules/undici/docs/docs/best-practices/mocking-request.md create mode 100644 node_modules/undici/docs/docs/best-practices/proxy.md create mode 100644 node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md create mode 100644 node_modules/undici/docs/docs/best-practices/writing-tests.md create mode 100644 node_modules/undici/docs/docs/getting-started.md create mode 100644 node_modules/undici/docs/docs/index.md create mode 100644 node_modules/undici/docs/docs/site.json create mode 100644 node_modules/undici/docs/docs/type-map.json create mode 100644 node_modules/undici/index-fetch.js create mode 100644 node_modules/undici/index.d.ts create mode 100644 node_modules/undici/index.js create mode 100644 node_modules/undici/lib/api/abort-signal.js create mode 100644 node_modules/undici/lib/api/api-connect.js create mode 100644 node_modules/undici/lib/api/api-pipeline.js create mode 100644 node_modules/undici/lib/api/api-request.js create mode 100644 node_modules/undici/lib/api/api-stream.js create mode 100644 node_modules/undici/lib/api/api-upgrade.js create mode 100644 node_modules/undici/lib/api/index.js create mode 100644 node_modules/undici/lib/api/readable.js create mode 100644 node_modules/undici/lib/cache/memory-cache-store.js create mode 100644 node_modules/undici/lib/cache/sqlite-cache-store.js create mode 100644 node_modules/undici/lib/core/connect.js create mode 100644 node_modules/undici/lib/core/constants.js create mode 100644 node_modules/undici/lib/core/diagnostics.js create mode 100644 node_modules/undici/lib/core/errors.js create mode 100644 node_modules/undici/lib/core/request.js create mode 100644 node_modules/undici/lib/core/socks5-client.js create mode 100644 node_modules/undici/lib/core/socks5-utils.js create mode 100644 node_modules/undici/lib/core/symbols.js create mode 100644 node_modules/undici/lib/core/tree.js create mode 100644 node_modules/undici/lib/core/util.js create mode 100644 node_modules/undici/lib/dispatcher/agent.js create mode 100644 node_modules/undici/lib/dispatcher/balanced-pool.js create mode 100644 node_modules/undici/lib/dispatcher/client-h1.js create mode 100644 node_modules/undici/lib/dispatcher/client-h2.js create mode 100644 node_modules/undici/lib/dispatcher/client.js create mode 100644 node_modules/undici/lib/dispatcher/dispatcher-base.js create mode 100644 node_modules/undici/lib/dispatcher/dispatcher.js create mode 100644 node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js create mode 100644 node_modules/undici/lib/dispatcher/env-http-proxy-agent.js create mode 100644 node_modules/undici/lib/dispatcher/fixed-queue.js create mode 100644 node_modules/undici/lib/dispatcher/h2c-client.js create mode 100644 node_modules/undici/lib/dispatcher/pool-base.js create mode 100644 node_modules/undici/lib/dispatcher/pool.js create mode 100644 node_modules/undici/lib/dispatcher/proxy-agent.js create mode 100644 node_modules/undici/lib/dispatcher/retry-agent.js create mode 100644 node_modules/undici/lib/dispatcher/round-robin-pool.js create mode 100644 node_modules/undici/lib/dispatcher/socks5-proxy-agent.js create mode 100644 node_modules/undici/lib/encoding/index.js create mode 100644 node_modules/undici/lib/global.js create mode 100644 node_modules/undici/lib/handler/cache-handler.js create mode 100644 node_modules/undici/lib/handler/cache-revalidation-handler.js create mode 100644 node_modules/undici/lib/handler/decorator-handler.js create mode 100644 node_modules/undici/lib/handler/deduplication-handler.js create mode 100644 node_modules/undici/lib/handler/redirect-handler.js create mode 100644 node_modules/undici/lib/handler/retry-handler.js create mode 100644 node_modules/undici/lib/interceptor/cache.js create mode 100644 node_modules/undici/lib/interceptor/decompress.js create mode 100644 node_modules/undici/lib/interceptor/deduplicate.js create mode 100644 node_modules/undici/lib/interceptor/dns.js create mode 100644 node_modules/undici/lib/interceptor/dump.js create mode 100644 node_modules/undici/lib/interceptor/redirect.js create mode 100644 node_modules/undici/lib/interceptor/response-error.js create mode 100644 node_modules/undici/lib/interceptor/retry.js create mode 100644 node_modules/undici/lib/llhttp/constants.d.ts create mode 100644 node_modules/undici/lib/llhttp/constants.js create mode 100644 node_modules/undici/lib/llhttp/llhttp-wasm.js create mode 100644 node_modules/undici/lib/llhttp/llhttp_simd-wasm.js create mode 100644 node_modules/undici/lib/llhttp/utils.d.ts create mode 100644 node_modules/undici/lib/llhttp/utils.js create mode 100644 node_modules/undici/lib/mock/mock-agent.js create mode 100644 node_modules/undici/lib/mock/mock-call-history.js create mode 100644 node_modules/undici/lib/mock/mock-client.js create mode 100644 node_modules/undici/lib/mock/mock-errors.js create mode 100644 node_modules/undici/lib/mock/mock-interceptor.js create mode 100644 node_modules/undici/lib/mock/mock-pool.js create mode 100644 node_modules/undici/lib/mock/mock-symbols.js create mode 100644 node_modules/undici/lib/mock/mock-utils.js create mode 100644 node_modules/undici/lib/mock/pending-interceptors-formatter.js create mode 100644 node_modules/undici/lib/mock/snapshot-agent.js create mode 100644 node_modules/undici/lib/mock/snapshot-recorder.js create mode 100644 node_modules/undici/lib/mock/snapshot-utils.js create mode 100644 node_modules/undici/lib/util/cache.js create mode 100644 node_modules/undici/lib/util/date.js create mode 100644 node_modules/undici/lib/util/runtime-features.js create mode 100644 node_modules/undici/lib/util/stats.js create mode 100644 node_modules/undici/lib/util/timers.js create mode 100644 node_modules/undici/lib/web/cache/cache.js create mode 100644 node_modules/undici/lib/web/cache/cachestorage.js create mode 100644 node_modules/undici/lib/web/cache/util.js create mode 100644 node_modules/undici/lib/web/cookies/constants.js create mode 100644 node_modules/undici/lib/web/cookies/index.js create mode 100644 node_modules/undici/lib/web/cookies/parse.js create mode 100644 node_modules/undici/lib/web/cookies/util.js create mode 100644 node_modules/undici/lib/web/eventsource/eventsource-stream.js create mode 100644 node_modules/undici/lib/web/eventsource/eventsource.js create mode 100644 node_modules/undici/lib/web/eventsource/util.js create mode 100644 node_modules/undici/lib/web/fetch/LICENSE create mode 100644 node_modules/undici/lib/web/fetch/body.js create mode 100644 node_modules/undici/lib/web/fetch/constants.js create mode 100644 node_modules/undici/lib/web/fetch/data-url.js create mode 100644 node_modules/undici/lib/web/fetch/formdata-parser.js create mode 100644 node_modules/undici/lib/web/fetch/formdata.js create mode 100644 node_modules/undici/lib/web/fetch/global.js create mode 100644 node_modules/undici/lib/web/fetch/headers.js create mode 100644 node_modules/undici/lib/web/fetch/index.js create mode 100644 node_modules/undici/lib/web/fetch/request.js create mode 100644 node_modules/undici/lib/web/fetch/response.js create mode 100644 node_modules/undici/lib/web/fetch/util.js create mode 100644 node_modules/undici/lib/web/infra/index.js create mode 100644 node_modules/undici/lib/web/subresource-integrity/Readme.md create mode 100644 node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js create mode 100644 node_modules/undici/lib/web/webidl/index.js create mode 100644 node_modules/undici/lib/web/websocket/connection.js create mode 100644 node_modules/undici/lib/web/websocket/constants.js create mode 100644 node_modules/undici/lib/web/websocket/events.js create mode 100644 node_modules/undici/lib/web/websocket/frame.js create mode 100644 node_modules/undici/lib/web/websocket/permessage-deflate.js create mode 100644 node_modules/undici/lib/web/websocket/receiver.js create mode 100644 node_modules/undici/lib/web/websocket/sender.js create mode 100644 node_modules/undici/lib/web/websocket/stream/websocketerror.js create mode 100644 node_modules/undici/lib/web/websocket/stream/websocketstream.js create mode 100644 node_modules/undici/lib/web/websocket/util.js create mode 100644 node_modules/undici/lib/web/websocket/websocket.js create mode 100644 node_modules/undici/package.json create mode 100644 node_modules/undici/scripts/strip-comments.js create mode 100644 node_modules/undici/types/README.md create mode 100644 node_modules/undici/types/agent.d.ts create mode 100644 node_modules/undici/types/api.d.ts create mode 100644 node_modules/undici/types/balanced-pool.d.ts create mode 100644 node_modules/undici/types/cache-interceptor.d.ts create mode 100644 node_modules/undici/types/cache.d.ts create mode 100644 node_modules/undici/types/client-stats.d.ts create mode 100644 node_modules/undici/types/client.d.ts create mode 100644 node_modules/undici/types/connector.d.ts create mode 100644 node_modules/undici/types/content-type.d.ts create mode 100644 node_modules/undici/types/cookies.d.ts create mode 100644 node_modules/undici/types/diagnostics-channel.d.ts create mode 100644 node_modules/undici/types/dispatcher.d.ts create mode 100644 node_modules/undici/types/dispatcher1-wrapper.d.ts create mode 100644 node_modules/undici/types/env-http-proxy-agent.d.ts create mode 100644 node_modules/undici/types/errors.d.ts create mode 100644 node_modules/undici/types/eventsource.d.ts create mode 100644 node_modules/undici/types/fetch.d.ts create mode 100644 node_modules/undici/types/formdata.d.ts create mode 100644 node_modules/undici/types/global-dispatcher.d.ts create mode 100644 node_modules/undici/types/global-origin.d.ts create mode 100644 node_modules/undici/types/h2c-client.d.ts create mode 100644 node_modules/undici/types/handlers.d.ts create mode 100644 node_modules/undici/types/header.d.ts create mode 100644 node_modules/undici/types/index.d.ts create mode 100644 node_modules/undici/types/interceptors.d.ts create mode 100644 node_modules/undici/types/mock-agent.d.ts create mode 100644 node_modules/undici/types/mock-call-history.d.ts create mode 100644 node_modules/undici/types/mock-client.d.ts create mode 100644 node_modules/undici/types/mock-errors.d.ts create mode 100644 node_modules/undici/types/mock-interceptor.d.ts create mode 100644 node_modules/undici/types/mock-pool.d.ts create mode 100644 node_modules/undici/types/patch.d.ts create mode 100644 node_modules/undici/types/pool-stats.d.ts create mode 100644 node_modules/undici/types/pool.d.ts create mode 100644 node_modules/undici/types/proxy-agent.d.ts create mode 100644 node_modules/undici/types/readable.d.ts create mode 100644 node_modules/undici/types/retry-agent.d.ts create mode 100644 node_modules/undici/types/retry-handler.d.ts create mode 100644 node_modules/undici/types/round-robin-pool.d.ts create mode 100644 node_modules/undici/types/snapshot-agent.d.ts create mode 100644 node_modules/undici/types/socks5-proxy-agent.d.ts create mode 100644 node_modules/undici/types/util.d.ts create mode 100644 node_modules/undici/types/utility.d.ts create mode 100644 node_modules/undici/types/webidl.d.ts create mode 100644 node_modules/undici/types/websocket.d.ts delete mode 100644 public/js/csv.js delete mode 100644 public/project-plan.html delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T06-36-48-511.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T06-41-14-950.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T06-41-14-963.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T06-43-13-525.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T06-47-01-551.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T07-09-35-889.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T07-10-17-549.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T07-10-17-555.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T07-12-27-276.db delete mode 100644 sessions/backups/projektuebersicht_2026-08-18T07-14-16-193.db delete mode 100644 sessions/backups/projektuebersicht_2026-09-30T16-34-30-986.db delete mode 100644 sessions/backups/projektuebersicht_2026-09-30T16-54-16-040.db delete mode 100644 sessions/backups/projektuebersicht_2026-09-30T17-34-47-236.db delete mode 100644 sessions/backups/projektuebersicht_2026-09-30T19-53-04-481.db delete mode 100644 sessions/backups/projektuebersicht_2026-09-30T20-01-56-518.db rename sessions/backups/{projektuebersicht_2026-09-30T18-07-31-950.db => projektuebersicht_2026-10-01T05-17-34-155.db} (98%) create mode 100644 sessions/backups/projektuebersicht_2026-10-01T05-18-05-602.db rename sessions/backups/{projektuebersicht_2026-09-30T18-29-56-513.db => projektuebersicht_2026-10-01T05-40-23-486.db} (99%) rename sessions/backups/{projektuebersicht_2026-09-30T18-11-50-966.db => projektuebersicht_2026-10-01T05-57-45-880.db} (99%) rename sessions/backups/{projektuebersicht_2026-09-30T19-04-26-077.db => projektuebersicht_2026-10-01T06-11-14-181.db} (99%) create mode 100644 sessions/backups/projektuebersicht_2026-10-01T06-20-46-331.db create mode 100644 sessions/backups/projektuebersicht_2026-10-01T06-39-56-328.db rename sessions/backups/{projektuebersicht_2026-09-30T22-33-04-487.db => projektuebersicht_2026-10-01T07-34-02-333.db} (87%) create mode 100644 sessions/backups/projektuebersicht_2026-10-01T07-37-46-981.db create mode 100644 sessions/backups/projektuebersicht_2026-10-01T08-18-09-891.db rename 26-4047 KfW SiKo_PlanKo_04_16.09.2026.pdf => sessions/uploads/234e470b-44c8-4b87-9061-05d1b5a971cb.pdf (100%) create mode 100644 sessions/uploads/36eeafdc-9e52-4204-b6cf-c98039131f89.pdf create mode 100644 sessions/uploads/3ed65781-10ed-43c3-9c0a-f159ed1fdb6f.pdf create mode 100644 sessions/uploads/6636ee95-c8fa-4c67-867a-b59cd00e8e25.pdf create mode 100644 sessions/uploads/780bee5e-4cb4-46db-86d6-85640de9a5bb.pdf create mode 100644 sessions/uploads/80240a19-b9cc-41dd-9f65-ab4340bfa4c5.pdf create mode 100644 sessions/uploads/a5e2c3ea-d0d9-4cbd-8983-74e1cbaadc66.pdf create mode 100644 sessions/uploads/e1171869-9c24-4f8c-a682-a0f91cc39ab4.pdf create mode 100644 sessions/uploads/e81cf06e-e07a-4075-a268-ecafb29e118d.pdf create mode 100644 sessions/uploads/ef310408-d7a2-44a2-ac86-79a9a774b44a.pdf diff --git a/.gitignore b/.gitignore index ec99bcc..9b901ee 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ sessions/*.db-shm # macOS-Artefakte .DS_Store +sessions/*.txt diff --git a/CLAUDE.md b/CLAUDE.md index 7248dbd..4318002 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,13 +9,56 @@ Single-page app for tracking engineering projects across weekly sessions step, no bundler, no tests. Data is persisted in a SQLite database (`node:sqlite`, needs Node >= 22.13) at `sessions/projektuebersicht.db`. -Two HTML entrypoints, both served statically by Express: -- `Test.html` — the main app (tabs: Projektboard, Auslastung, Projekte, - Projektbeteiligte). Served at `/`. -- `project-plan.html` — per-project Gantt/TODO planning view, opened in a new - tab via `openProjectPlan()` in `js/gantt.js`. Loads the shared helper modules - (`globals`, `utils`, `ui`, `date-picker`, `server`, `gantt`) plus the - plan modules under `js/plan/` — NOT the full main module set. +**ONE** HTML entrypoint, served statically by Express: +- `Test.html` — the whole app (sidebar nav items, in order: Meine Aufgaben, + **Projekte**, Wochenbericht, Auslastung, Projektbeteiligte, then + **Einstellungen** and — for admins only — **Admin**). Served at `/`. It + also contains the **Projektbereich** — the area of ONE project — as the + tab `plan` (`
`), with its own sub-navigation (`.plan-nav`) + over six sections: **Dashboard**, **Terminplan** (Gantt), **Aufgaben** + (task/milestone list), **Leistung** (erbrachter Leistungswert, see below), + **Dateien**, **Einstellungen** (project master data incl. honorar and the + beauftragte Leistungsphasen). Opened via `openProjectPlan()` in + `js/gantt.js`, which is now just `setPlanTarget()` + `showTab('plan')` — + the same kind of switch as Dashboard → Projekte, sidebar and topbar + stay put. + **There used to be a separate `Projekte` tab next to the Projektboard.** It + listed the very same projects a second time and offered a second editing + mask for the very same fields. Both are now ONE list — the tab `board`, + labelled **Projekte** (see "Eine Projektliste" below). The URL still reads `/Projektplan/?weekLabel=…&projNr=…` + and the selected section still lives in the URL fragment (`…#aufgaben`), + so both stay linkable. See "Projektbereich" below. +**`admin-users.html` no longer exists either.** It was a standalone +admin-only page with a hand-built header instead of the sidebar and its own +`checkAuthAndInit()` + role check. Its markup moved into `Test.html` as the +`benutzer` section of the Admin area, its ` - - -
-
-
-

Projektplan

-
-
-
- - -
-
- -
-
-
- - - - - - - - - - - - -
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/styles.css b/public/styles.css index 832c101..8024c81 100644 --- a/public/styles.css +++ b/public/styles.css @@ -20,6 +20,25 @@ --green: #248a3d; --amber: #b25000; --red: #d70015; + --red-hover: #e8112d; + --red-active: #b8000f; + + /* Flächen des Anwesenheits-Kacheln im Zustand "anwesend" — eigene Tokens, + weil eine feste rgba-Grünfläche im Dark Mode nicht trägt. */ + --attend-bg: rgba(36,138,61,.10); + --attend-bg-hover: rgba(36,138,61,.16); + + /* Kopfband der Wochenbericht-Karten je Änderungsart. Das Band trägt weiße + Schrift, deshalb etwas dunkler als --green (#248a3d käme nur auf + Kontrast 4,4 : 1 und bliebe damit unter den 4,5 : 1 für normalen Text). */ + --band-neu: #0071e3; + --band-fortschritt: #22823a; + + /* Gelbe Markierung neu hinzugekommener Protokoll-Punkte — bewusst eigenes + Token statt --band-neu (dort blau, bedeutet "neu kommentiertes Projekt" + im Wochenbericht — eine andere Bedeutung, die hier kollidieren würde). */ + --protokoll-neu-bg: #fff6db; + --protokoll-neu-border: #e6b800; --r-sm: 6px; --r-md: 8px; @@ -52,45 +71,75 @@ body { .container { padding: 20px 24px; } -/* === Kopfzeile & Navigation === */ -header { +/* === App-Shell: globales Menüband oben + linke Navigations-Sidebar + + Hauptbereich (OpenProject-Anordnung: ein durchgehendes globales Menüband + trägt Marke, aktuellen Kontext [Sitzung] und alle Aktionen/Konto rechts; + die Sidebar darunter ist reine Modul-Navigation, sonst nichts) === */ +.app-shell { display: flex; flex-direction: column; min-height: 100vh; } +.app-body { display: flex; flex: 1; min-height: 0; } +.app-main { flex: 1; min-width: 0; } + +/* Generisches Icon-System (js/icons.js): Icons skalieren wie ein Zeichen mit + der umgebenden Schriftgröße (1.1em), erben die Textfarbe über currentColor. */ +.icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; } +.icon svg { width: 1.1em; height: 1.1em; display: block; } + +/* === Globales Menüband (immer sichtbar, alle Breiten) === */ +.app-topbar { + position: sticky; + top: 0; + z-index: 140; + display: flex; + align-items: center; + flex-wrap: wrap; + row-gap: 8px; + gap: 12px; + padding: 8px 20px; background: rgba(255,255,255,.82); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px); border-bottom: 1px solid var(--line-soft); - color: var(--text); - padding: 10px 24px; } -.header-bar { - position: relative; - display: flex; - align-items: center; - gap: 16px; - min-height: 48px; -} +.mobile-nav-toggle { display: inline-flex; } +@media (min-width: 769px) { .app-topbar .mobile-nav-toggle { display: none; } } -header h1 { - font-size: 19px; +.topbar-brand { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } +.topbar-logo { height: 28px; width: auto; object-fit: contain; } +.topbar-title { + font-size: 15px; font-weight: 600; margin: 0; - letter-spacing: -0.02em; + letter-spacing: -0.01em; white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding-right: 140px; } -.header-logo { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - height: 44px; - width: auto; - max-width: 120px; - object-fit: contain; - pointer-events: none; + +.app-topbar-actions { + display: flex; + align-items: center; + gap: 6px; + margin-left: auto; + flex-wrap: wrap; +} + +.toolbar-group { display: flex; align-items: center; gap: 4px; } + +.toolbar-divider { + width: 1px; + height: 22px; + background: var(--line-soft); + flex-shrink: 0; +} + +@media (max-width: 768px) { + .app-topbar-actions { + order: 4; + flex: 1 1 100%; + margin-left: 0; + overflow-x: auto; + padding-bottom: 2px; + } } .header-toggle { @@ -103,20 +152,98 @@ header h1 { justify-content: center; } -.header-toggle-icon { font-size: 16px; } - -@media (max-width: 520px) { - .header-logo { height: 36px; max-width: 80px; } - header h1 { font-size: 16px; padding-right: 100px; } +.theme-toggle-circle { + width: 34px; + height: 34px; + border-radius: 50%; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--surface-sunk); + border: 1px solid var(--line-soft); + color: var(--text); + cursor: pointer; + flex-shrink: 0; + transition: background .2s var(--ease), color .2s var(--ease), transform .1s var(--ease); } -/* === Seiten-Menü (links ausfahrend) === */ +.theme-toggle-circle:hover { background: var(--line-soft); } +.theme-toggle-circle:active { transform: scale(.95); } + +.icon-btn-danger:hover { background: var(--red); color: #fff; } +.icon-btn-danger:active { background: var(--red-active); } + +/* Konto-Menü (Avatar-Knopf rechts im Menüband + Dropdown) */ +.topbar-user { position: relative; } + +.user-menu-dropdown { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 220px; + background: var(--surface); + border: 1px solid var(--line-soft); + border-radius: var(--r-lg); + box-shadow: var(--sh-3); + padding: 8px; + display: flex; + flex-direction: column; + gap: 2px; + z-index: 150; + opacity: 0; + visibility: hidden; + transform: translateY(-4px); + transition: opacity .15s var(--ease), transform .15s var(--ease), visibility .15s var(--ease); +} + +.user-menu-dropdown.open { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.user-menu-info { + padding: 8px 10px 10px; + margin-bottom: 2px; + border-bottom: 1px solid var(--line-soft); + font-size: 12px; + color: var(--text-muted); +} + +/* .menu-item: generischer Zeilen-Knopf/-Link (Text + optionales Icon) — + genutzt im Konto-Dropdown (dort über .user-menu-dropdown volle Breite) UND + als Basis für die Sidebar-Unterpunkte (siehe .sidebar-subitem). */ +a.menu-item, +button.menu-item { + display: inline-flex; + align-items: center; + gap: 8px; + text-align: left; + background: transparent; + color: var(--text); + padding: 8px 10px; + border: none; + border-radius: var(--r-md); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background .15s var(--ease); +} + +a.menu-item:hover, +button.menu-item:hover { background: var(--surface-sunk); } + +.user-menu-dropdown a.menu-item, +.user-menu-dropdown button.menu-item { display: flex; width: 100%; } + +/* === Seiten-Navigation (links, reine Modul-Navigation) === */ .side-menu { position: fixed; top: 0; left: 0; bottom: 0; - width: 280px; + width: 260px; max-width: 80vw; background: var(--surface); border-right: 1px solid var(--line-soft); @@ -132,70 +259,42 @@ header h1 { transform: translateX(0); } -.side-menu-header { +.side-menu-mobile-header { display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 18px; - border-bottom: 1px solid var(--line-soft); - font-weight: 600; - font-size: 16px; + justify-content: flex-end; + padding: 8px 8px 0; +} +@media (min-width: 769px) { .side-menu-mobile-header { display: none; } } + +.sidebar-nav { + flex: 1; + min-height: 0; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 2px; + padding: 12px; } -.side-menu-header-actions { - display: flex; - align-items: center; - gap: 8px; -} - -.theme-toggle-circle { - width: 34px; - height: 34px; - border-radius: 50%; - padding: 0; - display: inline-flex; +.sidebar-collapse-bar { + flex-shrink: 0; + display: none; align-items: center; justify-content: center; - background: var(--surface-sunk); - border: 1px solid var(--line-soft); - color: var(--text); - cursor: pointer; - transition: background .2s var(--ease), transform .1s var(--ease); -} - -.theme-toggle-circle:hover { background: var(--line-soft); } -.theme-toggle-circle:active { transform: scale(.95); } - -.side-menu-content { - padding: 16px; - display: flex; - flex-direction: column; - gap: 18px; - overflow-y: auto; -} - -.menu-section { - display: flex; - flex-direction: column; - gap: 8px; -} - -.menu-label { - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: .04em; - color: var(--text-muted); - margin-bottom: 2px; -} - -.menu-section button { width: 100%; justify-content: center; } - -.menu-divider { + width: 100%; + padding: 12px; + background: transparent; border: none; border-top: 1px solid var(--line-soft); - margin: 0; + border-radius: 0; + color: var(--text-muted); + cursor: pointer; + transition: background .2s var(--ease), color .2s var(--ease); } +.sidebar-collapse-bar:hover { background: var(--surface-sunk); color: var(--text); } +.sidebar-collapse-bar:active { transform: none; } + +#sidebarCollapseIcon { transition: transform .2s var(--ease); } /* === Show Mode === */ .show-mode-overlay { @@ -216,6 +315,8 @@ header h1 { border-bottom: 1px solid var(--line-soft); font-size: 18px; font-weight: 600; + line-height: 1.2; + letter-spacing: -0.01em; } .show-mode-content { @@ -254,33 +355,6 @@ header h1 { font-size: 12px; } -.show-mode-content .table-wrap { - max-height: none; - overflow: visible; -} - -.show-mode-content table { - font-size: 10px !important; -} - -.show-mode-content th, -.show-mode-content td { - padding: 3px 4px !important; -} - -.show-mode-content thead tr:nth-child(1) th, -.show-mode-content thead tr:nth-child(2) th { - position: static; -} - -.show-mode-content th.rot { - height: 60px !important; -} - -.show-mode-content .proj-name { - min-width: 120px !important; -} - .show-mode-content .status { min-width: 160px !important; } @@ -291,55 +365,28 @@ header h1 { font-size: 10px !important; } -.show-mode-content .person-cell { - width: 22px !important; -} - -.show-mode-content .lph-cell { - width: 38px !important; -} - -.show-mode-content .lph-cell input { - width: 30px !important; -} - .show-mode-content .bar { height: 3px !important; margin-top: 2px !important; } -.show-mode-content .group-header td { - padding: 4px 6px !important; -} - -.show-mode-content #boardLegend { - display: none; -} .show-mode-content .load-grid { - gap: 12px; + gap: 14px; } -.show-mode-content .load-row { - grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)) !important; - gap: 10px !important; +/* Im Show Mode enger: Die Auslastung wird dort auf eine Wandfläche geworfen, + auf der alle Rollen zusammen sichtbar bleiben sollen. */ +.show-mode-content .load-zeile { + padding: 7px 4px !important; } -.show-mode-content .load-card { - padding: 10px 12px !important; -} - -.show-mode-content .load-card h3 { +.show-mode-content .load-wer-name { font-size: 12px !important; } -.show-mode-content .load-num { - font-size: 22px !important; -} - -.show-mode-content .load-detail { - font-size: 10px !important; - margin-top: 6px !important; +.show-mode-content .load-wert { + font-size: 16px !important; } .show-mode-loading { @@ -358,8 +405,8 @@ header h1 { } /* Show Mode: Bearbeitung deaktivieren */ -.show-mode-content .person-cell, -.show-mode-content .lph-cell input, +.show-mode-content .board-avatar, +.show-mode-content .board-lph-row input, .show-mode-content .status-edit { pointer-events: none !important; cursor: default !important; @@ -395,6 +442,66 @@ header h1 { visibility: visible; } +/* Ab 769px: Sidebar dauerhaft sichtbar (kein Off-Canvas mehr nötig) */ +@media (min-width: 769px) { + #headerMenu.side-menu { + position: sticky; + /* top muss der tatsächlichen Höhe von .app-topbar entsprechen (beide + sticky, gleicher Scroll-Container) — sonst zieht die Sidebar bei + Seiten, die länger als ein Bildschirm sind, beim Herunterscrollen mit + top:0 unter das Menüband statt sich direkt darunter einzureihen, und + am unteren Rand bleibt eine sidebarlose Lücke in Höhe des Menübands + übrig (height: calc(100vh - 52px) reichte dann nicht bis nach unten). */ + top: 52px; + height: calc(100vh - 52px); + transform: none; + box-shadow: none; + width: 260px; + flex: 0 0 260px; + border-right: 1px solid var(--line-soft); + } + #headerMenu.side-menu .header-toggle { display: none; } + .side-menu-backdrop { display: none; } + + /* Umschalt-Leiste am unteren Sidebar-Rand nur auf Desktop-Breite sichtbar + (auf Mobile bleibt es beim reinen Off-Canvas-Menü ohne Zwischenzustand) */ + .sidebar-collapse-bar { display: flex; } + + /* Eingeklappte Sidebar: nur noch eine schmale Icon-Rail mit den 5 + Navigationspunkten, alle Beschriftungen verschwinden */ + #headerMenu.side-menu.collapsed { + width: 64px; + flex: 0 0 64px; + } + + #headerMenu.side-menu.collapsed .sidebar-nav { + padding-left: 8px; + padding-right: 8px; + } + + #headerMenu.side-menu.collapsed .nav-item .nav-label { + display: none; + } + + #headerMenu.side-menu.collapsed .nav-item { + justify-content: center; + padding-left: 0; + padding-right: 0; + } + + #headerMenu.side-menu.collapsed #sidebarCollapseIcon { + transform: rotate(180deg); + } + + /* In der Icon-Rail ergeben aufgeklappte Unterlisten/Rubrik-Überschriften + ohne Textbeschriftung keinen Sinn — beides wird vollständig ausgeblendet. */ + #headerMenu.side-menu.collapsed .nav-chevron, + #headerMenu.side-menu.collapsed .sidebar-section-label, + #headerMenu.side-menu.collapsed .sidebar-subnav { + display: none; + } +} + .toolbar { display: flex; gap: 8px; @@ -408,36 +515,17 @@ button.active { border-color: var(--accent); } -.week-date { - color: var(--text-muted); - font-size: 13px; - margin-left: 4px; -} - -/* Abteilungs-Tabs: Segmented Control */ -.dept-tabs { - position: relative; - display: flex; - align-items: stretch; - justify-content: center; - min-height: 54px; - background: var(--bg); - padding: 0; -} - +/* Hamburger-Knopf im globalen Menüband (nur < 769px sichtbar, öffnet die + Sidebar als Off-Canvas-Menü, siehe .mobile-nav-toggle weiter oben). */ .menu-corner-btn { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 54px; display: inline-flex; align-items: center; justify-content: center; - background: var(--bg); - border: none; - border-right: 1px solid var(--line-soft); - border-radius: 0; + width: 40px; + height: 40px; + background: var(--surface-sunk); + border: 1px solid var(--line-soft); + border-radius: var(--r-md); box-shadow: none; color: var(--text); cursor: pointer; @@ -445,72 +533,122 @@ button.active { transition: background .2s var(--ease); } -.menu-corner-btn:hover { background: var(--surface-sunk); } +.menu-corner-btn:hover { background: var(--line-soft); } .menu-corner-btn:active { transform: none; } -.dept-tabs-center { +/* Hauptnavigation (Sidebar) — ersetzt die alte horizontale .tabs-Leiste */ +.sidebar-nav { gap: 2px; } + +.nav-item { display: flex; align-items: center; - gap: 2px; - flex-wrap: wrap; - padding: 12px 24px 0 24px; -} - -.dept-tab { + gap: 8px; + width: 100%; + text-align: left; + text-decoration: none; background: transparent; color: var(--text-muted); - padding: 10px 22px; + padding: 9px 12px; cursor: pointer; border: none; border-radius: var(--r-md); - font-size: 15px; + font-size: 13px; font-weight: 500; transition: background .2s var(--ease), color .2s var(--ease); } -.dept-tab:hover { - background: rgba(0,0,0,.045); - color: var(--text); +.nav-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + flex-shrink: 0; + font-size: 15px; } -.dept-tab.active { - background: var(--surface); - color: var(--text); - font-weight: 600; - box-shadow: var(--sh-1); +.nav-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -/* Unter-Tabs */ -.tabs { - display: flex; - gap: 2px; - background: var(--bg); - padding: 6px 24px 0 24px; - flex-wrap: wrap; - border-bottom: 1px solid var(--line-soft); -} +/* .nav-item kann ein echter -Link sein statt eines Buttons — ohne + diese Regel würde ein bereits besuchtes Ziel (echte Browser-Historie) mit + der UA-Standardfarbe für besuchte Links abweichen. */ +.nav-item:visited { color: var(--text-muted); } +.nav-item:hover, +.nav-item:visited:hover { background: var(--surface-sunk); color: var(--text); } -.tab { - background: transparent; - color: var(--text-muted); - padding: 8px 14px; - cursor: pointer; - border: none; - border-radius: var(--r-sm) var(--r-sm) 0 0; - font-size: 13px; - font-weight: 500; - transition: color .2s var(--ease), box-shadow .2s var(--ease); -} - -.tab:hover { color: var(--text); } - -.tab.active { - background: transparent; +.nav-item.active { + background: var(--accent-soft); color: var(--accent); font-weight: 600; - box-shadow: inset 0 -2px 0 var(--accent); } +/* .sidebar-section-label: kleine Rubrik-Überschrift oberhalb einer Gruppe von + Sidebar-Einträgen (z. B. "Dashboard", "Navigation"). */ +.sidebar-section-label { + padding: 10px 12px 2px; + font-size: 11px; + font-weight: 700; + letter-spacing: .04em; + text-transform: uppercase; + color: var(--text-muted); +} + +/* .nav-group-toggle: Sidebar-Eintrag, der statt eines Tabs einen + auf-/zuklappbaren Unterbereich steuert (Einstellungen/Admin, siehe + toggleSidebarAccordion() in js/ui.js). */ +.nav-chevron { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + flex-shrink: 0; + margin-left: auto; + transition: transform .2s var(--ease); +} + +.nav-group-toggle.expanded .nav-chevron { transform: rotate(180deg); } + +/* .sidebar-subnav: Liste der Aktionen unter einem aufgeklappten + .nav-group-toggle, per max-height animiert ein-/ausgeblendet. */ +.sidebar-subnav { + display: flex; + flex-direction: column; + gap: 1px; + padding-left: 8px; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height .25s var(--ease), opacity .2s var(--ease); +} + +.sidebar-subnav.open { + /* Reicht für die längste Gruppe („Einstellungen", sieben Punkte); die + Animation läuft über max-height, deshalb steht hier eine feste Zahl. */ + max-height: 360px; + opacity: 1; +} + +.sidebar-subitem { + width: 100%; + padding-left: 34px; + font-size: 12.5px; + color: var(--text-muted); +} +.sidebar-subitem:hover { color: var(--text); } +/* .is-active: der gerade gezeigte Abschnitt der Gruppe „Einstellungen" + (showSettingsSection() in js/settings/settings.js). Die übrigen Gruppen + lösen Aktionen aus und kennen deshalb keinen dauerhaft aktiven Punkt. */ +.sidebar-subitem.is-active { + color: var(--accent); + font-weight: 600; + background: var(--accent-soft); +} +.sidebar-subitem.danger { color: var(--red); } +.sidebar-subitem.danger:hover { background: var(--red); color: #fff; } + /* === Buttons & Formulare === */ button { cursor: pointer; @@ -543,8 +681,8 @@ button.secondary:hover { background: rgba(0,0,0,.10); } button.secondary:active { background: rgba(0,0,0,.14); } button.danger { background: var(--red); } -button.danger:hover { background: #e8112d; } -button.danger:active { background: #b8000f; } +button.danger:hover { background: var(--red-hover); } +button.danger:active { background: var(--red-active); } button.small { padding: 3px 10px; @@ -553,6 +691,7 @@ button.small { select, input[type=text], +input[type=email], input[type=date] { padding: 7px 10px; border: 1px solid var(--line); @@ -566,6 +705,7 @@ input[type=date] { select:focus, input[type=text]:focus, +input[type=email]:focus, input[type=date]:focus { outline: none; border-color: var(--accent); @@ -627,46 +767,8 @@ thead tr:nth-child(1) th { z-index: 10; } -thead tr:nth-child(2) th { - background: var(--surface-alt); - color: var(--text); - font-weight: 500; - border: 1px solid var(--line); - position: sticky; - top: 0; /* Wird von adjustStickyOffsets() dynamisch überschrieben */ - z-index: 6; -} - -th.rot { - background: var(--surface); - writing-mode: vertical-rl; - transform: rotate(180deg); - height: 90px; - padding: 4px 2px; - font-weight: 500; -} - -td.left { - text-align: left; - min-width: 80px; -} - tr:nth-child(even) td { background: var(--surface-alt); } -.proj-name { - font-weight: 600; - min-width: 160px; - letter-spacing: -0.01em; -} - -.group-header td { - background: var(--surface-sunk) !important; - font-weight: 600; - text-align: left; - color: var(--text); - letter-spacing: -0.01em; -} - /* === Status-Felder in der Projektzeile === */ .status { min-width: 240px; @@ -704,6 +806,157 @@ tr:nth-child(even) td { background: var(--surface-alt); } .status-edit b, .status-edit strong { font-weight: 600; } +/* === Kommentar-Editor im Board und im Wochenbericht ====================== + Ersetzt das frühere contenteditable-Feld samt execCommand-Leiste: Textfeld + mit Verfasser-Kopfzeile, ausdrücklichem Speichern und dem eingeklappten, + scrollbaren Verlauf der früheren Sitzungen. Aufgebaut von + renderCommentEditor() in js/board/board.js. */ + +.comment-block { display: flex; flex-direction: column; gap: 10px; } + +.comment-editor { + border: 1px solid var(--line); + border-radius: var(--r-md); + background: var(--surface); + overflow: hidden; +} +.comment-editor-head, +.comment-head-line { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + padding: 6px 10px; + font-size: 11px; + color: var(--text-muted); + background: var(--surface-alt); + border-bottom: 1px solid var(--line-soft); +} +.comment-head-line { border: 1px solid var(--line-soft); border-radius: var(--r-md); } +.comment-who { display: flex; align-items: center; gap: 6px; color: var(--text); font-weight: 500; } +.comment-unknown { color: var(--text-subtle); font-weight: 400; font-style: italic; } +.comment-sep { color: var(--text-subtle); } +.comment-av { + width: 19px; height: 19px; border-radius: 50%; + background: var(--accent); color: #fff; + display: flex; align-items: center; justify-content: center; + font-size: 9px; font-weight: 600; flex-shrink: 0; +} +.comment-kbd { margin-left: auto; font-size: 10px; color: var(--text-subtle); white-space: nowrap; } + +.comment-editor textarea { + width: 100%; + display: block; + border: none; + border-radius: 0; + padding: 8px 10px; + font-family: inherit; + font-size: 12px; + line-height: 1.5; + color: var(--text); + background: var(--surface); + resize: vertical; + min-height: 62px; +} +.comment-editor textarea:focus { outline: none; box-shadow: none; } +.comment-editor textarea::placeholder { color: var(--text-subtle); } + +.comment-editor-foot { + display: flex; + align-items: center; + gap: 7px; + padding: 7px 10px; + border-top: 1px solid var(--line-soft); + background: var(--surface-alt); +} +.comment-foot-spacer { margin-left: auto; } +.comment-dirty { font-size: 10.5px; color: var(--amber); white-space: nowrap; } + +.comment-readonly { + border: 1px solid var(--line-soft); + border-radius: var(--r-md); + padding: 7px 9px; + font-size: 11.5px; + line-height: 1.45; + background: var(--surface); +} + +/* Verlauf: eingeklappt, mit Anzahl im Kopf; die Liste selbst scrollt */ +.comment-history { + border: 1px solid var(--line-soft); + border-radius: var(--r-md); + background: var(--surface); + overflow: hidden; +} +.comment-history > summary { + cursor: pointer; + list-style: none; + display: flex; + align-items: center; + gap: 8px; + padding: 7px 10px; + font-size: 11.5px; + color: var(--text); + user-select: none; +} +.comment-history > summary::-webkit-details-marker { display: none; } +.comment-history > summary:hover { background: var(--surface-alt); } +.comment-history-caret { + color: var(--text-subtle); + font-size: 9px; + transition: transform .18s var(--ease); +} +.comment-history[open] > summary .comment-history-caret { transform: rotate(90deg); } +.comment-history-count { + margin-left: auto; + font-size: 10.5px; + font-weight: 600; + background: var(--accent-soft); + color: var(--accent); + border-radius: var(--r-pill); + padding: 1px 8px; + font-variant-numeric: tabular-nums; +} +.comment-history-count.zero { background: var(--surface-sunk); color: var(--text-subtle); } +.comment-history-body { border-top: 1px solid var(--line-soft); padding: 4px 10px 8px; } + +.comment-history-list { + max-height: 220px; + overflow-y: auto; + overscroll-behavior: contain; +} +.comment-history-entry { padding: 8px 0; } +.comment-history-entry + .comment-history-entry { border-top: 1px solid var(--line-soft); } +.comment-history-head { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 3px; + font-size: 10.5px; +} +.comment-history-week { + font-weight: 600; + color: var(--text); + white-space: nowrap; + font-variant-numeric: tabular-nums; +} +.comment-history-meta { color: var(--text-subtle); } +.comment-history-text { font-size: 11.5px; line-height: 1.45; color: var(--text-muted); } +.comment-history-text b, +.comment-history-text strong { font-weight: 600; color: var(--text); } + +.comment-take { + margin-left: auto; + padding: 2px 9px; + font-size: 10px; + border-radius: var(--r-pill); + background: transparent; + color: var(--text-muted); + border: 1px solid var(--line); + flex-shrink: 0; +} +.comment-take:hover { background: var(--surface-sunk); color: var(--text); } + .status-toolbar { display: none; position: absolute; @@ -745,26 +998,6 @@ tr:nth-child(even) td { background: var(--surface-alt); } font-weight: 600; } -/* === Personen-Zuweisungszellen === */ -.person-cell { - cursor: pointer; - user-select: none; - width: 26px; - transition: background .12s var(--ease); -} - -.person-cell.on { - background: var(--green) !important; - color: #fff; - font-weight: 600; -} - -.person-cell.optional { - background: #d18b00 !important; - color: #fff; - font-weight: 600; -} - .locked-cell { cursor: not-allowed !important; background: var(--surface-sunk) !important; @@ -777,70 +1010,6 @@ tr:nth-child(even) td { background: var(--surface-alt); } border-color: var(--line-soft) !important; } -/* Gesperrte Zellen immer grau, auch wenn X/(X) gesetzt ist */ -.person-cell.locked-cell.on, -.person-cell.locked-cell.optional { - background: var(--surface-sunk) !important; - color: var(--text-muted) !important; -} - -/* === Rollenfarben === */ -.person-cell.role-planung { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(212, 245, 224); - background-attachment: fixed; -} - -.person-cell.role-projektleitung { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(255, 219, 219); - background-attachment: fixed; -} - -.person-cell.role-bauüberwachung { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(203, 238, 255); - background-attachment: fixed; -} - -.role-planung, -.role-planung.rot { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(212, 245, 224); - background-attachment: fixed; -} - -.role-projektleitung, -.role-projektleitung.rot { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(255, 219, 219); - background-attachment: fixed; -} - -.role-bauüberwachung, -.role-bauüberwachung.rot { - background: linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)), - rgb(203, 238, 255); - background-attachment: fixed; -} - -th.role-planung, -th.role-projektleitung, -th.role-bauüberwachung { color: var(--text); } - -/* === LPH / Balken / Überschriften === */ -.lph-cell { width: 50px; } - -.lph-cell input { - width: 38px; - border: 1px solid var(--line-soft); - border-radius: var(--r-sm); - padding: 3px; - text-align: center; - font-size: 11px; - font-family: inherit; -} - .bar { height: 5px; border-radius: var(--r-pill); @@ -856,16 +1025,449 @@ th.role-bauüberwachung { color: var(--text); } transition: width .3s var(--ease); } -/* Markierung der amtierenden Leistungsphase (gesetzt im Tab "Projekte") */ -.lph-cell.lp-aktiv { - background: var(--accent-soft); - box-shadow: inset 0 2px 0 var(--accent); +/* === Projektboard (Listenansicht) === + Eine Bubble-Card je Projektgruppe (.board-list) mit den Projekten als + Listenzeilen (.board-row) — ersetzt die frühere breite Matrix (eine Spalte + je Person/Leistungsphase). Jede Zeile zeigt nur das Nötigste auf einen + Blick (Kürzel-Avatare der Zugeordneten, aktive Phase, Status-Vorschau); + Klick auf die Zeile klappt darunter das Detailpanel (.board-drawer) mit + Status-Editor, vollständiger Rollen-Zuordnung und allen LPH-Balken auf. */ +.board-list { + display: flex; + flex-direction: column; + background: var(--surface); + border: 1px solid var(--line-soft); + border-radius: var(--r-xl); + box-shadow: var(--sh-2); + overflow: hidden; } -.lph-cell.lp-aktiv input { - border-color: var(--accent); - font-weight: 600; +.board-row { border-bottom: 1px solid var(--line-soft); } +.board-row:last-child { border-bottom: none; } + +.board-row-main { + display: flex; + align-items: center; + gap: 14px; + padding: 12px 16px; + cursor: pointer; + transition: background .15s var(--ease); } +.board-row-main:hover { background: var(--surface-sunk); } + +.board-chev { + flex-shrink: 0; + color: var(--text-subtle); + display: inline-flex; + transition: transform .2s var(--ease); +} +.board-row.open .board-chev { transform: rotate(90deg); } + +.board-nr { + flex-shrink: 0; + width: 64px; + font-size: 11.5px; + color: var(--text-muted); + font-family: ui-monospace, 'SF Mono', monospace; +} + +.board-name-wrap { flex: 1 1 260px; min-width: 0; } +.board-name { + font-weight: 600; + letter-spacing: -0.01em; + cursor: pointer; +} +.board-name:hover { color: var(--accent); text-decoration: underline; } + +.board-status-line { + margin-top: 3px; + font-size: 12px; + color: var(--text-muted); + display: flex; + align-items: center; + gap: 6px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.board-newdot { + flex-shrink: 0; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--green); +} + +.board-avatar-stack { + flex-shrink: 0; + display: flex; + flex-wrap: wrap; + gap: 4px; + max-width: 220px; +} + +.board-avatar { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--text-muted); + background: var(--surface-sunk); + border: 1px solid var(--line-soft); + cursor: pointer; + user-select: none; + transition: background .15s var(--ease), color .15s var(--ease), box-shadow .15s var(--ease); +} + +.board-avatar.role-planung.state-1 { background: var(--green); border-color: var(--green); color: #fff; } +.board-avatar.role-projektleitung.state-1 { background: var(--red); border-color: var(--red); color: #fff; } +.board-avatar.role-bauüberwachung.state-1 { background: var(--accent); border-color: var(--accent); color: #fff; } + +.board-avatar.role-planung.state-2 { background: transparent; color: var(--green); border-color: var(--green); } +.board-avatar.role-projektleitung.state-2 { background: transparent; color: var(--red); border-color: var(--red); } +.board-avatar.role-bauüberwachung.state-2 { background: transparent; color: var(--accent); border-color: var(--accent); } + +.phase-pill { + flex-shrink: 0; + font-size: 11px; + font-weight: 600; + font-family: ui-monospace, 'SF Mono', monospace; + padding: 4px 10px; + border-radius: var(--r-pill); + background: var(--accent-soft); + color: var(--accent); + white-space: nowrap; +} +.phase-pill.muted { background: var(--surface-sunk); color: var(--text-subtle); font-family: inherit; font-weight: 500; } + +/* --- Detailpanel (.board-drawer) --- */ +.board-drawer { + padding: 18px 20px 22px 94px; + background: var(--surface-alt); + border-top: 1px solid var(--line-soft); +} + +.board-drawer h4 { + margin: 0 0 8px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .05em; + color: var(--text-muted); +} + +/* Die Schublade liefert drei gleichrangige Blöcke (Kommentar, Leistungsphasen, + Beteiligte, siehe renderBoardDrawer() in js/board/board.js). Im Projektboard + stehen Kommentar und Leistungsphasen wie bisher übereinander in der linken + Spalte, die Beteiligten rechts daneben. */ +.board-drawer-grid { + display: grid; + grid-template-columns: 1.3fr 1fr; + grid-template-areas: + "kommentar beteiligte" + "lph beteiligte"; + align-content: start; + gap: 16px 24px; +} +.drawer-col-comment { grid-area: kommentar; } +.drawer-col-lph { grid-area: lph; } +.drawer-col-people { grid-area: beteiligte; } + +@media (max-width: 720px) { + .board-drawer-grid { + grid-template-columns: 1fr; + grid-template-areas: "kommentar" "lph" "beteiligte"; + } +} + +/* Auswahl der beauftragten Leistungsphasen. Genutzt vom Honorar-Dialog der + Projektplan-Seite (renderHonorarEingaben(), js/plan/plan-honorar.js) und + vom Neues-Projekt-Modal (openNewProjModal(), js/load/load.js) — deshalb + hier im gemeinsamen Stylesheet statt inline in einer der beiden Seiten. */ +.beauftragte-lp-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); + gap: 4px 10px; +} +.beauftragte-lp-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text); + white-space: nowrap; +} + +/* --- Wochenbericht: immer aufgeklappte Projekt-Karten ("Neu kommentiert", + "Fortschritt aktualisiert") — im Unterschied zu .board-list/.board-row + (Akkordeon der "Unverändert"-Gruppe) ist hier jede Karte ein eigenständiger + Kasten, kein Container mit Trennlinien. */ +.report-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.report-card { + background: var(--surface); + border: 1px solid var(--line-soft); + border-radius: var(--r-md); + box-shadow: var(--sh-1); + overflow: hidden; +} + +/* Kopfband: durchgefärbt in der Farbe der Änderungsart, damit beim Scrollen + ohne Blick auf die Abschnitts-Überschrift erkennbar ist, worum es geht. + Die Farbe kommt aus --band-neu/--band-fortschritt und lässt sich dort in + einem Zug abschwächen, falls die Leiste zu kräftig wirkt. */ +.report-card-head { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + padding: 9px 14px; + background: var(--surface-alt); + border-bottom: 1px solid var(--line-soft); +} +.report-card-head .board-name { cursor: pointer; } + +.report-card-neu > .report-band { background: var(--band-neu); } +.report-card-fortschritt > .report-band { background: var(--band-fortschritt); } + +.report-band { + border-bottom: none; + color: #fff; +} +.report-band .board-nr { color: rgba(255,255,255,.75); } +.report-band .board-name { color: #fff; font-weight: 600; } +.report-band .board-name:hover { text-decoration: underline; } +.report-band .tag-chip { + background: rgba(255,255,255,.22); + color: #fff; + border-color: transparent; +} +.report-band .report-reason-badge { + background: rgba(255,255,255,.22); + color: #fff; +} + +/* Drei Spalten statt zwei: im Bericht stehen Leistungsphasen und Beteiligte + nebeneinander, wodurch die Karte flacher wird. Dasselbe Markup wie im + Projektboard, nur anders angeordnet. */ +.report-card .board-drawer { + padding: 14px 16px 16px; + border-top: none; + background: var(--surface); +} +.report-card .board-drawer-grid { + grid-template-columns: 1.7fr 1fr 1fr; + grid-template-areas: "kommentar lph beteiligte"; + gap: 22px; +} +.report-card .board-role-label { width: 58px; } + +@media (max-width: 860px) { + .report-card .board-drawer-grid { + grid-template-columns: 1fr; + grid-template-areas: "kommentar" "lph" "beteiligte"; + gap: 16px; + } +} + +/* === Dichte Fassung der Karte (.board-drawer-dicht, Wochenbericht) === + Gleiche Angaben, gleiche Bedienelemente, enger gesetzt. Den Ausschlag gibt + die Leistungsphasen-Leiste: als Zeilen untereinander bestimmten bis zu zehn + Phasen die Höhe der ganzen Karte — bei 29 Projekten wurde der Bericht damit + rund 17 Bildschirmseiten lang. Nebeneinander braucht dieselbe Information + eine Zeile. */ +.board-drawer-dicht .board-drawer-grid { + /* Die Beteiligten brauchen so viel Breite wie der Kommentar: Sobald ihre + Kürzel umbrechen, bestimmt ihre Spalte die Höhe der ganzen Karte. Mit + 1.45/1.15/1 waren es 14,2 Bildschirmseiten, mit gleich breiter + Beteiligten-Spalte 11,6 — mehr Breite bringt darüber hinaus nichts, + weil dann ohnehin nichts mehr umbricht. */ + /* Die rechte Spalte bekommt eine MINDESTBREITE: Zehn Phasenfelder in einer + reinen fr-Spalte wurden 27 px schmal, und "100" passte nicht mehr ins + Eingabefeld — 79 Werte waren abgeschnitten. Unter 620 px Containerbreite + gibt die Mindestbreite nach und die Spalten rutschen untereinander. */ + grid-template-columns: minmax(0, 1fr) minmax(340px, 1fr); + grid-template-areas: "kommentar kompakt"; + gap: 18px; +} +@media (max-width: 860px) { + .board-drawer-dicht .board-drawer-grid { + grid-template-columns: 1fr; + grid-template-areas: "kommentar" "kompakt"; + gap: 14px; + } +} +.drawer-col-kompakt { grid-area: kompakt; min-width: 0; } +/* Die zweite Überschrift in derselben Spalte braucht Abstand nach oben, den + die erste (am Spaltenanfang) nicht haben darf. */ +.drawer-h4-zweit { margin-top: 12px; } + +.board-lph-leiste { + display: flex; + gap: 3px; + align-items: stretch; +} +.board-lph-cell { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 1px; + padding: 3px 2px 4px; + border: 1px solid var(--line-soft); + border-radius: var(--r-sm); + background: var(--surface-sunk); + /* Der Füllstand kommt als senkrechter Verlauf aus dem Inline-Stil + (renderBoardDrawer in js/board/board.js) — so bleibt der Balken erhalten, + den die Zeilenform als eigenes Element hatte. */ + background-repeat: no-repeat; +} +.board-lph-cell-label { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + line-height: 1.2; +} +.board-lph-cell input { + width: 100%; + min-width: 0; + padding: 1px 2px; + border: none; + background: transparent; + text-align: center; + font-size: 11.5px; + font-variant-numeric: tabular-nums; + color: var(--text); + font-family: inherit; +} +.board-lph-cell input:focus { + outline: 1.5px solid var(--accent); + outline-offset: -1px; + border-radius: 3px; +} +.board-lph-cell.lp-aktiv { + border-color: var(--accent); +} +.board-lph-cell.lp-aktiv .board-lph-cell-label, +.board-lph-cell.lp-aktiv input { color: var(--accent); } +.board-lph-cell.locked-cell input { cursor: default; } + +/* Das Rollen-Label steht NEBEN seinen Kürzeln. Über ihnen kostete es drei + zusätzliche Zeilen, und damit bestimmte die Beteiligten-Spalte die Höhe der + ganzen Karte (189 px gegenüber 130 für den Kommentar). Die feste Breite ist + so bemessen, dass "Bauüberwachung" hineinpasst — bei 52 px lief die + Beschriftung über die Kürzel. + + Die Regeln hängen am Merkmal .board-drawer-dicht, nicht am Ort: Sie + standen zunächst unter .report-card, und mit dem Umbau des Wochenberichts + auf die Tagesordnung gab es diesen Container nicht mehr — die dichte + Karte fiel still auf die Board-Darstellung zurück. Das zusätzliche + .board-role-mini liefert die Spezifität, die nötig ist, um die dortige + Regel für .board-role-row zu schlagen. */ +.board-drawer-dicht .board-role-mini .board-role-row { + display: flex; + align-items: flex-start; + gap: 7px; + margin-bottom: 4px; +} +.board-drawer-dicht .board-role-mini .board-role-label { + flex: 0 0 auto; + /* Bemessen am längsten Rollennamen: "BAUÜBERWACHUNG" braucht bei 10 px in + Versalien diese Breite — bei 88 px fehlte der letzte Buchstabe. */ + width: 104px; + padding-top: 4px; + font-size: 10px; + line-height: 1.25; + letter-spacing: .02em; + text-transform: uppercase; +} + +/* Schmale Fenster: Dort reicht die Breite für Label UND Kürzel nicht mehr, + also wieder übereinander — lieber drei Zeilen mehr als abgeschnittene + Beschriftungen. */ +@media (max-width: 1100px) { + .board-drawer-dicht .board-role-mini .board-role-row { display: block; } + .board-drawer-dicht .board-role-mini .board-role-label { + width: auto; + padding-top: 0; + margin-bottom: 2px; + } +} + +.report-reason-badge { + margin-left: auto; + font-size: 11px; + font-weight: 600; + padding: 3px 10px; + border-radius: var(--r-pill); + background: var(--accent-soft); + color: var(--accent); + white-space: nowrap; +} +/* Innerhalb einer Report-Karte braucht das Detailpanel nicht den + Zeilen-Einzug/die obere Trennlinie, die es im Board-Akkordeon ausrichtet. */ +.report-card .board-drawer { + padding: 16px 20px 20px; + border-top: none; +} + +.board-lph-list { + display: flex; + flex-direction: column; + gap: 7px; + margin-top: 14px; +} + +.board-lph-row { + display: grid; + grid-template-columns: 42px 1fr 50px; + align-items: center; + gap: 10px; +} + +.board-lph-row .bar { flex: 1; margin-top: 0; } + +.board-lph-label { + font-size: 11px; + font-family: ui-monospace, 'SF Mono', monospace; + color: var(--text-muted); +} + +.board-lph-row input { + width: 100%; + border: 1px solid var(--line-soft); + border-radius: var(--r-sm); + padding: 3px 6px; + text-align: center; + font-size: 11px; + font-family: inherit; + background: var(--surface); + color: var(--text); +} + +.board-lph-row.lp-aktiv .board-lph-label { color: var(--accent); font-weight: 600; } +.board-lph-row.lp-aktiv input { border-color: var(--accent); font-weight: 600; } + +.board-role-mini { display: flex; flex-direction: column; gap: 10px; } +.board-role-mini .board-role-row { display: flex; align-items: center; gap: 10px; } +.board-role-mini .board-role-label { + width: 100px; + flex-shrink: 0; + font-size: 11.5px; + color: var(--text-muted); +} +.board-role-mini .board-role-chips { display: flex; flex-wrap: wrap; gap: 6px; } .section-title { font-size: 17px; @@ -873,51 +1475,266 @@ th.role-bauüberwachung { color: var(--text); } margin: 24px 0 10px; color: var(--text); letter-spacing: -0.02em; + line-height: 1.2; } /* === Lade-/Auswertungsübersicht === */ .load-grid { display: flex; flex-direction: column; - gap: 20px; + gap: 24px; width: 100%; } .load-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 14px; + gap: 16px; align-items: start; width: 100%; } +/* === Auslastung: das Lastband === + Eine Zeile je Person und Rolle; der Balken ist nach Projekten aufgeteilt. + Zuvor stand jede Person in einer eigenen .load-card mit ihrer Projektliste + klein darunter — man sah die Zahl, aber nicht, woraus sie besteht. Die + Frage nach einer Auslastung von 1,80 ist aber immer dieselbe: aus einem + großen Projekt oder aus fünf kleinen, und was davon lässt sich abgeben? + .load-card/.load-row/.load-num/.load-detail bleiben bestehen — das + Dashboard (js/dashboard/dashboard.js) und die Ressourcenansicht des + Projektbereichs (js/plan/plan-gantt.js) bauen darauf auf. */ +.load-rolle { width: 100%; } + +.load-rolle-titel { + font-size: 14px; + font-weight: 600; + margin: 0 0 6px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: .04em; +} + +.load-zeile { + display: flex; + align-items: center; + gap: 16px; + padding: 11px 4px; + border-bottom: 1px solid var(--line-soft); +} +.load-zeile:last-child { border-bottom: none; } +.load-zeile:hover { background: var(--surface); } + +/* Wer — Name oben, Kürzel und Zusätze darunter. Feste Breite, damit die + Bänder aller Zeilen an derselben Stelle beginnen und vergleichbar sind. */ +.load-wer { + flex: 0 0 200px; + min-width: 0; +} +.load-wer-name { + font-size: 13px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.load-wer-meta { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; + font-size: 11px; + color: var(--text-subtle); + margin-top: 1px; +} + +/* Das Band. Die Segmente füllen es von links; was frei bleibt, ist die + Restkapazität bis zum Skalenende (Vorgabe 8, firmenweit einstellbar). */ +.load-band { + flex: 1; + display: flex; + align-items: center; + height: 22px; + min-width: 140px; + background: var(--surface-sunk); + border-radius: var(--r-sm); + overflow: hidden; +} + +.load-seg { + display: flex; + align-items: center; + height: 100%; + padding: 0 7px; + min-width: 3px; + font-size: 10px; + font-weight: 600; + color: #fff; + white-space: nowrap; + overflow: hidden; + border-right: 1px solid rgba(255,255,255,.4); + transition: filter .15s var(--ease); +} +.load-seg:last-of-type { border-right: none; } +.load-seg:hover { filter: brightness(1.12); } +.load-seg span { overflow: hidden; } + +.load-band-leer { + font-size: 11px; + color: var(--text-subtle); + padding-left: 9px; +} + +.load-wert { + flex: 0 0 62px; + text-align: right; + font-size: 19px; + font-weight: 600; + letter-spacing: -.02em; + font-variant-numeric: tabular-nums; +} + +.load-anbahnung { + font-size: 10px; + padding: 1px 7px; + border-radius: var(--r-pill); + background: var(--surface-sunk); + color: var(--text-muted); + white-space: nowrap; +} + +/* Kopfzeile: was die Zahlen abdecken. Sie steht oben, weil die Aussagekraft + der ganzen Seite daran hängt — eine Auslastung aus drei von 29 Projekten + ist etwas anderes als eine aus 29. */ +.load-kopf { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; + padding: 12px 14px; + background: var(--surface-sunk); + border-radius: var(--r-lg); + font-size: 13px; +} +.load-kopf-zahl strong { + font-size: 17px; + font-variant-numeric: tabular-nums; +} +.load-kopf-warnung { + color: var(--red); + font-weight: 600; +} +.load-kopf-warnung::before { + content: ''; + display: inline-block; + width: 7px; height: 7px; + border-radius: 50%; + background: var(--red); + margin-right: 7px; + vertical-align: middle; +} + +.load-luecken { + padding: 12px 14px; + border: 1px dashed var(--line); + border-radius: var(--r-lg); + font-size: 12px; +} +.load-luecke { + display: flex; + justify-content: space-between; + gap: 16px; + padding: 4px 0; + border-bottom: 1px solid var(--line-soft); +} +.load-luecke:last-child { border-bottom: none; } +.load-luecke b { color: var(--text-muted); font-weight: 500; white-space: nowrap; } + +/* Summenzeile je Mensch: ein durchgehender Riegel statt der Projektsegmente. + Sie beantwortet die Frage, die die Rollenzeilen nicht beantworten können — + wer in drei Rollen arbeitet, hat trotzdem nur eine Arbeitswoche. */ +.load-zeile-summe .load-wer-name { font-weight: 600; } +.load-seg-summe { border-right: none; } + +/* Marke der vollen Kapazität im Band. Sie sitzt am rechten Rand, weil das + volle Band genau die Wochenkapazität ist; ein Riegel, der sie erreicht, + stößt sichtbar an. */ +.load-kapazitaet { + position: absolute; + right: 0; top: 0; bottom: 0; + width: 2px; + background: var(--text-subtle); + opacity: .5; +} +.load-zeile-summe .load-band { position: relative; } + +.load-einheit { + font-size: 11px; + font-weight: 500; + color: var(--text-subtle); + margin-left: 2px; +} + +.load-legende-item em { + font-style: normal; + color: var(--text-subtle); + margin-left: 3px; +} + +/* Legende der Projektfarben, nur für die tatsächlich vorkommenden Projekte. + Nötig, weil in schmalen Segmenten die Projektnummer keinen Platz findet. */ +.load-legende { + display: flex; + flex-wrap: wrap; + gap: 6px 16px; + padding-top: 14px; + border-top: 1px solid var(--line-soft); + font-size: 11px; + color: var(--text-muted); +} +.load-legende-item { + display: inline-flex; + align-items: center; + gap: 6px; +} +.load-legende-item i { + width: 9px; + height: 9px; + border-radius: 2px; + flex: 0 0 auto; +} + +@media (max-width: 720px) { + .load-wer { flex-basis: 128px; } + .load-zeile { gap: 10px; } + .load-wert { flex-basis: 52px; font-size: 16px; } +} + .load-row-title { grid-column: 1 / -1; width: 100%; - font-size: 20px; + font-size: 14px; font-weight: 600; margin: 0 0 2px 0; padding-top: 4px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; - font-size: 14px; } .load-card { background: var(--surface); border: 1px solid var(--line-soft); - border-radius: var(--r-lg); - padding: 16px 18px; + border-radius: var(--r-xl); + padding: 20px 22px; min-width: 0; width: 100%; - box-shadow: var(--sh-1); + box-shadow: var(--sh-2); transition: box-shadow .2s var(--ease), transform .2s var(--ease); } .load-card:hover { - box-shadow: var(--sh-2); - transform: translateY(-1px); + box-shadow: var(--sh-3); + transform: translateY(-2px); } .load-card h3 { @@ -925,6 +1742,7 @@ th.role-bauüberwachung { color: var(--text); } font-size: 14px; font-weight: 600; letter-spacing: -0.01em; + line-height: 1.25; } .load-num { @@ -960,7 +1778,12 @@ th.role-bauüberwachung { color: var(--text); } display: none; align-items: center; justify-content: center; - z-index: 100; + /* Über Menüband (.app-topbar, 140) UND Seitenleiste (.side-menu, 200): + Mit dem früheren Wert 100 blieb die Navigation neben einem offenen Dialog + sichtbar und anklickbar — auch hinter dem "blockierenden" Login-Fenster — + und die Seitenleiste verdeckte bei mittleren Fensterbreiten die linke + Kante breiter Dialoge. */ + z-index: 300; } .modal { @@ -979,6 +1802,7 @@ th.role-bauüberwachung { color: var(--text); } font-size: 20px; font-weight: 600; letter-spacing: -0.02em; + line-height: 1.15; } .form-row { @@ -994,6 +1818,59 @@ th.role-bauüberwachung { color: var(--text); } color: var(--text-muted); } +/* Gesperrte Bedienelemente: Ohne eigene :disabled-Regeln schlagen die + Farbangaben oben die Browser-Voreinstellung, und ein deaktivierter Knopf + sieht exakt aus wie ein bedienbarer (z. B. in gesperrten Sitzungen oder + bei der eigenen Zeile in der Benutzerverwaltung). */ +button:disabled, +input:disabled, +select:disabled, +textarea:disabled { + opacity: .45; + cursor: not-allowed; + box-shadow: none; +} +button:disabled:hover, +select:disabled:hover { + filter: none; + transform: none; +} + +/* Warnband unten rechts, wenn das Speichern fehlschlägt (showSyncProblem() in + js/store.js). Liegt über allem, damit es auch neben einem offenen Dialog + sichtbar bleibt. */ +.sync-problem-bar { + position: fixed; + right: 18px; + bottom: 18px; + z-index: 500; + max-width: 340px; + background: var(--red); + color: #fff; + font-size: 13px; + line-height: 1.45; + padding: 11px 15px; + border-radius: var(--r-md); + box-shadow: var(--sh-3); +} + +/* Hinweisband gesperrter Sitzungen (renderBoard()/renderWochenbericht()). + Vorher standen die Farben als Inline-Angaben im Markup und waren damit im + Dunkelmodus grell weiß-rosa. */ +.lock-notice { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + background: var(--surface-alt); + border: 1px solid var(--red); + color: var(--red); + border-radius: var(--r-md); + padding: 9px 13px; + margin-bottom: 12px; + font-size: 13px; +} + /* === LP-Aufwand-Maske als Matrix-Tabelle === Zeilen = Leistungsphasen, Spalten = Rollen + "amtierend" (Radio). */ .lp-matrix { @@ -1043,25 +1920,38 @@ th.role-bauüberwachung { color: var(--text); } max-width: 640px; } +/* .bubble-card: gemeinsame "Bubble-Card"-Optik für Projekt- und + Personen-Karten (Projekte-/Projektbeteiligte-Tab) — gleicher Radius/ + Schatten wie .load-card (Dashboard/Auslastung), aber ohne Hover-Anhebung, + weil die Projekt-Karten Formularelemente enthalten (eine Anhebung beim + Überfahren wäre dort irritierend). */ +.bubble-card { + background: var(--surface); + border: 1px solid var(--line-soft); + border-radius: var(--r-xl); + padding: 20px 22px; + box-shadow: var(--sh-2); + transition: box-shadow .2s var(--ease); +} + +.bubble-card:hover { + box-shadow: var(--sh-3); +} + .person-list-item, .person-card { display: flex; flex-direction: column; - border-bottom: 1px solid var(--line-soft); width: 100%; } .person-list-item { align-items: center; gap: 8px; - padding: 8px 4px; overflow-x: auto; flex-direction: row; } -.person-card:last-child, -.person-list-item:last-child { border-bottom: none; } - .person-header { display: flex; align-items: center; @@ -1069,10 +1959,43 @@ th.role-bauüberwachung { color: var(--text); } padding: 8px 4px; cursor: pointer; overflow-x: auto; + transition: background .2s var(--ease); } .person-header:hover { background: var(--surface-sunk); } +/* Schaltflächen-Optik auch für Links: Ein in einer + Knopfleiste sah sonst aus wie ein blauer Standardlink, weil .secondary + ausschließlich für