1135 lines
52 KiB
HTML
1135 lines
52 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Rolling Agenda (Prototyp V14 Fix: Person Ratings)</title>
|
|
<style>
|
|
:root {
|
|
--bg-color: #1e1e1e;
|
|
--card-bg: #2d2d2d;
|
|
--text-color: #e0e0e0;
|
|
--accent: #007acc;
|
|
--danger: #d9534f;
|
|
--success: #5cb85c;
|
|
--warning: #f0ad4e;
|
|
--info: #5bc0de;
|
|
--muted: #888;
|
|
--border-color: #444;
|
|
--tag-bg: #44475a;
|
|
--project-tag-bg: #6c5ce7;
|
|
--person-ref-bg: #e17055;
|
|
--sidebar-bg: #252526;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* SIDEBAR */
|
|
.sidebar {
|
|
width: 250px;
|
|
background-color: var(--sidebar-bg);
|
|
border-right: 1px solid var(--border-color);
|
|
padding: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.sidebar-title {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
color: var(--accent);
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
border-bottom: 2px solid var(--accent);
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.nav-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.nav-item {
|
|
padding: 10px;
|
|
margin-bottom: 5px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
color: #ccc;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: #fff;
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: var(--accent);
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.nav-section-header {
|
|
font-size: 0.8em;
|
|
color: #888;
|
|
text-transform: uppercase;
|
|
margin-top: 15px;
|
|
margin-bottom: 5px;
|
|
padding-left: 5px;
|
|
}
|
|
|
|
/* MAIN CONTENT */
|
|
.main-content {
|
|
flex-grow: 1;
|
|
padding: 20px;
|
|
overflow-y: auto;
|
|
position: relative;
|
|
}
|
|
|
|
.container {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
border-bottom: 2px solid var(--accent);
|
|
padding-bottom: 10px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.mode-switch {
|
|
background: #333;
|
|
padding: 5px;
|
|
border-radius: 20px;
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.mode-btn {
|
|
padding: 8px 16px;
|
|
border-radius: 15px;
|
|
border: none;
|
|
background: transparent;
|
|
color: #aaa;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
transition: 0.3s;
|
|
}
|
|
|
|
.mode-btn.active {
|
|
background: var(--accent);
|
|
color: white;
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.mode-btn.active.meeting-mode {
|
|
background: var(--warning);
|
|
color: #222;
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
margin-top: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.tab-btn {
|
|
background-color: #333;
|
|
color: #aaa;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
.tab-btn.active {
|
|
background-color: #444;
|
|
color: white;
|
|
border-bottom: 2px solid var(--accent);
|
|
}
|
|
|
|
.view-controls {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.toggle-compact-btn {
|
|
background: transparent;
|
|
border: 1px solid #555;
|
|
color: #ccc;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.toggle-compact-btn:hover {
|
|
background: #333;
|
|
}
|
|
|
|
.input-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin-bottom: 30px;
|
|
background: #252526;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
input[type="text"],
|
|
textarea {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid #444;
|
|
background-color: #1e1e1e;
|
|
color: white;
|
|
font-family: Consolas, monospace;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
textarea {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
button {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.btn-success {
|
|
background-color: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning {
|
|
background-color: var(--warning);
|
|
color: #222;
|
|
}
|
|
|
|
.btn-info {
|
|
background-color: var(--info);
|
|
color: #222;
|
|
}
|
|
|
|
.meta-editor {
|
|
background-color: #252526;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #444;
|
|
}
|
|
|
|
.meta-field {
|
|
margin-bottom: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.meta-field label {
|
|
font-size: 0.9em;
|
|
color: #aaa;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.meta-field input {
|
|
background: #111;
|
|
color: #ddd;
|
|
border: 1px solid #555;
|
|
}
|
|
|
|
|
|
.topic-card {
|
|
background-color: var(--card-bg);
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
border-left: 5px solid var(--accent);
|
|
transition: all 0.2s;
|
|
cursor: move;
|
|
}
|
|
|
|
.topic-card.dragging {
|
|
opacity: 0.4;
|
|
border: 2px dashed #aaa;
|
|
}
|
|
|
|
.topic-card.drag-over-top {
|
|
border-top: 4px solid var(--accent);
|
|
margin-top: 24px;
|
|
transition: margin 0.2s;
|
|
}
|
|
|
|
.topic-card.drag-over-bottom {
|
|
border-bottom: 4px solid var(--accent);
|
|
margin-bottom: 24px;
|
|
transition: margin 0.2s;
|
|
}
|
|
|
|
.topic-card.status-new {
|
|
border-left-color: var(--success);
|
|
}
|
|
|
|
.topic-card.status-snoozed {
|
|
opacity: 0.6;
|
|
border-left-color: var(--muted);
|
|
cursor: default;
|
|
}
|
|
|
|
.topic-card.status-processed {
|
|
opacity: 0.6;
|
|
border-left-color: var(--muted);
|
|
background: #222;
|
|
cursor: default;
|
|
}
|
|
|
|
.new-badge {
|
|
background: var(--success);
|
|
color: white;
|
|
font-size: 0.7em;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
margin-left: 10px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.processed-badge {
|
|
background: #444;
|
|
color: #aaa;
|
|
font-size: 0.7em;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.topic-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px 20px;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.topic-header:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.topic-title {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.title-edit-btn {
|
|
opacity: 0;
|
|
color: #aaa;
|
|
margin-left: 10px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
.topic-header:hover .title-edit-btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.collapse-icon {
|
|
margin-right: 10px;
|
|
font-size: 0.8em;
|
|
color: #aaa;
|
|
transition: transform 0.2s;
|
|
display: inline-block;
|
|
}
|
|
|
|
.topic-card.collapsed .collapse-icon {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.topic-card.collapsed .topic-body {
|
|
display: none;
|
|
}
|
|
|
|
.topic-card.collapsed {
|
|
border-bottom: 1px solid #444;
|
|
}
|
|
|
|
.topic-body {
|
|
padding: 0 20px 20px 20px;
|
|
border-top: 1px solid #444;
|
|
cursor: default;
|
|
}
|
|
|
|
.meeting-controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
padding-top: 15px;
|
|
border-top: 1px solid #444;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.meeting-controls button {
|
|
flex: 1;
|
|
font-size: 0.9em;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.history {
|
|
margin-bottom: 15px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
background: #222;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid #333;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.history-item {
|
|
margin-bottom: 15px;
|
|
border-bottom: 1px solid #333;
|
|
padding-bottom: 10px;
|
|
position: relative;
|
|
}
|
|
|
|
.history-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.history-item:hover {
|
|
background: #2a2a2a;
|
|
}
|
|
|
|
.history-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.history-date {
|
|
color: var(--accent);
|
|
font-size: 0.8em;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.edit-history-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #666;
|
|
cursor: pointer;
|
|
font-size: 1.2em;
|
|
opacity: 0;
|
|
padding: 0 5px;
|
|
}
|
|
|
|
.history-item:hover .edit-history-btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.edit-history-btn:hover {
|
|
color: #fff;
|
|
}
|
|
|
|
.edit-area {
|
|
width: 100%;
|
|
background: #111;
|
|
color: #ddd;
|
|
border: 1px solid var(--accent);
|
|
padding: 10px;
|
|
font-family: Consolas, monospace;
|
|
min-height: 100px;
|
|
}
|
|
|
|
.edit-actions {
|
|
display: flex;
|
|
gap: 5px;
|
|
margin-top: 5px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
ul.tree-list {
|
|
list-style-type: none;
|
|
padding-left: 20px;
|
|
margin: 0;
|
|
border-left: 1px solid #444;
|
|
}
|
|
|
|
ul.tree-list li {
|
|
position: relative;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.toggle-btn {
|
|
position: absolute;
|
|
left: -18px;
|
|
top: 2px;
|
|
width: 14px;
|
|
height: 14px;
|
|
line-height: 12px;
|
|
text-align: center;
|
|
font-size: 10px;
|
|
background: #444;
|
|
color: #fff;
|
|
cursor: pointer;
|
|
border-radius: 2px;
|
|
user-select: none;
|
|
}
|
|
|
|
.toggle-btn:hover {
|
|
background: #666;
|
|
}
|
|
|
|
.collapsed>ul {
|
|
display: none;
|
|
}
|
|
|
|
.collapsed-list>.toggle-btn::after {
|
|
content: "+";
|
|
}
|
|
|
|
.expanded-list>.toggle-btn::after {
|
|
content: "-";
|
|
}
|
|
|
|
.list-content {
|
|
color: #ddd;
|
|
}
|
|
|
|
|
|
.assignment-tag {
|
|
background-color: var(--tag-bg);
|
|
color: #fff;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
font-size: 0.9em;
|
|
border: 1px solid #6272a4;
|
|
display: inline-block;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.project-tag {
|
|
background-color: var(--project-tag-bg);
|
|
color: #fff;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
font-size: 0.9em;
|
|
display: inline-block;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.arrow-highlight {
|
|
color: var(--warning);
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* PERSON REFERENCE (@Name) */
|
|
.person-ref-tag {
|
|
background-color: var(--person-ref-bg);
|
|
color: #fff;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
font-size: 0.9em;
|
|
display: inline-block;
|
|
margin-left: 5px;
|
|
cursor: pointer;
|
|
border: 1px solid transparent;
|
|
position: relative;
|
|
}
|
|
|
|
.person-ref-tag:hover {
|
|
border-color: #fff;
|
|
}
|
|
|
|
/* RATING COLORS */
|
|
.rating-1 {
|
|
border-bottom: 3px solid #d63031;
|
|
}
|
|
|
|
/* Ganz schlecht */
|
|
.rating-2 {
|
|
border-bottom: 3px solid #fab1a0;
|
|
}
|
|
|
|
/* Schlecht */
|
|
.rating-3 {
|
|
border-bottom: 3px solid #00b894;
|
|
}
|
|
|
|
/* Gut */
|
|
.rating-4 {
|
|
border-bottom: 3px solid #00cec9;
|
|
box-shadow: 0 0 5px #00cec9;
|
|
}
|
|
|
|
/* Sehr gut */
|
|
|
|
/* HOVER DIALOG */
|
|
.rating-dialog {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: #333;
|
|
border: 1px solid #555;
|
|
padding: 20px;
|
|
z-index: 1000;
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
}
|
|
|
|
.rating-dialog h3 {
|
|
color: #fff;
|
|
margin-top: 0;
|
|
}
|
|
|
|
.rating-btn {
|
|
display: block;
|
|
width: 100%;
|
|
margin-bottom: 10px;
|
|
padding: 10px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.rb-1 {
|
|
background-color: #d63031;
|
|
}
|
|
|
|
.rb-2 {
|
|
background-color: #e17055;
|
|
}
|
|
|
|
.rb-3 {
|
|
background-color: #00b894;
|
|
}
|
|
|
|
.rb-4 {
|
|
background-color: #0984e3;
|
|
}
|
|
|
|
.rating-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 999;
|
|
}
|
|
|
|
.view-section {
|
|
display: none;
|
|
}
|
|
|
|
.view-section.active {
|
|
display: block;
|
|
}
|
|
|
|
.journal-day {
|
|
margin-bottom: 30px;
|
|
border-left: 2px solid #555;
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.journal-date-header {
|
|
font-size: 1.3em;
|
|
color: var(--accent);
|
|
margin-bottom: 15px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.journal-entry {
|
|
margin-bottom: 15px;
|
|
background: #2d2d2d;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.journal-topic-ref {
|
|
font-size: 0.8em;
|
|
color: var(--muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 4px;
|
|
display: block;
|
|
}
|
|
|
|
.person-card,
|
|
.dashboard-view-card {
|
|
background-color: #2d2d2d;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
border-left: 5px solid var(--info);
|
|
}
|
|
|
|
.dashboard-view-card {
|
|
border-left-color: var(--project-tag-bg);
|
|
}
|
|
|
|
.person-header {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
color: var(--info);
|
|
border-bottom: 1px solid #444;
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.dashboard-view-header {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
color: #a29bfe;
|
|
border-bottom: 1px solid #444;
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.person-task {
|
|
margin-bottom: 10px;
|
|
padding-left: 10px;
|
|
border-left: 2px solid #444;
|
|
}
|
|
|
|
.person-task-ref {
|
|
font-size: 0.8em;
|
|
color: #aaa;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.agenda-divider {
|
|
color: #888;
|
|
font-size: 0.9em;
|
|
margin: 30px 0 10px 0;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
border-bottom: 1px solid #444;
|
|
padding-bottom: 5px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="sidebar">
|
|
<div class="sidebar-title">My Contexts</div>
|
|
|
|
<div class="nav-section-header">General</div>
|
|
<ul class="nav-list" id="nav-general"></ul>
|
|
|
|
<div class="nav-section-header">Jour Fixes</div>
|
|
<ul class="nav-list" id="nav-contexts"></ul>
|
|
<div class="nav-section-header">Projects</div>
|
|
<ul class="nav-list" id="nav-projects"></ul>
|
|
<div class="nav-section-header">People</div>
|
|
<ul class="nav-list" id="nav-people"></ul>
|
|
</div>
|
|
|
|
<div class="main-content">
|
|
<div class="container">
|
|
<h1 id="page-title">
|
|
<span id="context-title">Loading...</span>
|
|
<div class="mode-switch" id="mode-switch-container">
|
|
<button class="mode-btn active" id="btn-prep" onclick="setMode('prep')">🛠️ Vorbereitung</button>
|
|
<button class="mode-btn" id="btn-meet" onclick="setMode('meeting')">🎤 Meeting</button>
|
|
</div>
|
|
</h1>
|
|
|
|
<div class="tabs" id="tabs-container">
|
|
<button class="tab-btn active" onclick="switchView('agenda')" id="tab-agenda">🔥 Agenda /
|
|
Eingabe</button>
|
|
<button class="tab-btn" onclick="switchView('journal')" id="tab-journal">📜 Journal / Historie</button>
|
|
<button class="tab-btn" onclick="switchView('persons')" id="tab-persons">👥 Personen (All)</button>
|
|
<button class="tab-btn" onclick="switchView('snoozed')" id="tab-snoozed">💤 Verschoben</button>
|
|
<button class="tab-btn" onclick="switchView('dashboard')" id="tab-dashboard" style="display:none">📊
|
|
Dashboard</button>
|
|
<div class="view-controls">
|
|
<button class="toggle-compact-btn" onclick="toggleAllCompact()" id="btn-compact">🔽 Alle
|
|
einklappen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="view-agenda" class="view-section active">
|
|
<div class="input-group">
|
|
<label style="font-size:0.9em;color:#aaa;">Neues Thema / Log-Eintrag erstellen:</label>
|
|
<input type="text" id="newTopicInput" placeholder="Titel des Themas..."
|
|
onkeypress="handleTopicEnter(event)">
|
|
<button class="btn-primary" onclick="addTopic()">+ Thema anlegen</button>
|
|
</div>
|
|
<div id="agenda-open-header" class="agenda-divider" style="display:none;">Aktuell / Offen</div>
|
|
<div id="agendaList"></div>
|
|
<div id="agenda-processed-header" class="agenda-divider" style="display:none;">Bereits abgelegt heute
|
|
</div>
|
|
<div id="processedList"></div>
|
|
</div>
|
|
|
|
<div id="view-journal" class="view-section">
|
|
<div id="journalList"></div>
|
|
</div>
|
|
<div id="view-persons" class="view-section">
|
|
<div id="personsList"></div>
|
|
</div>
|
|
<div id="view-snoozed" class="view-section">
|
|
<div id="snoozedList"></div>
|
|
</div>
|
|
<div id="view-dashboard" class="view-section">
|
|
<div id="metaEditorContainer"></div>
|
|
<div id="dashboardContent"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rating Modal -->
|
|
<div id="rating-modal" style="display:none;">
|
|
<div class="rating-overlay" onclick="closeRating()"></div>
|
|
<div class="rating-dialog">
|
|
<h3 id="rating-title">Rate Interaction</h3>
|
|
<p style="color:#aaa; font-size:0.9em;">Wie war die Zusammenarbeit?</p>
|
|
<button class="rating-btn rb-1" onclick="submitRating(1)">😡 Ganz schlecht</button>
|
|
<button class="rating-btn rb-2" onclick="submitRating(2)">🙁 Schlecht</button>
|
|
<button class="rating-btn rb-3" onclick="submitRating(3)">🙂 Gut</button>
|
|
<button class="rating-btn rb-4" onclick="submitRating(4)">🤩 Sehr gut</button>
|
|
<button style="margin-top:10px; background:transparent; border:none; color:#aaa; cursor:pointer;"
|
|
onclick="closeRating()">Abbrechen</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// --- STATE & DATA MODEL ---
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
|
|
const dayBeforeYesterday = new Date(Date.now() - 172800000).toISOString().split('T')[0];
|
|
const lastWeek = new Date(Date.now() - 604800000).toISOString().split('T')[0];
|
|
|
|
// Templates
|
|
const tplProject = { status: "#stInPlanning", owner: "", links: "" };
|
|
const tplPerson = { fullName: "", email: "", phone: "", duSince: "" };
|
|
|
|
let appData = {
|
|
contexts: [
|
|
{ id: 'daily-log', name: '📝 Daily Log / Inbox', type: 'meeting' },
|
|
{ id: 'jf-sysadmins', name: 'JF Team Sysadmins', type: 'meeting' },
|
|
{ id: 'jf-devs', name: 'JF Developer', type: 'meeting' },
|
|
{ id: 'p-tisax', name: 'Project TISAX', type: 'project', meta: { status: "#stInArbeit", owner: "STEFE", links: "FileServer/Projects/TISAX" } },
|
|
{ id: 'p-cloud-migration', name: 'Project CLOUD-MIGRATION', type: 'project', meta: { status: "#stBlocked", owner: "CHFI", links: "Jira-1234" } },
|
|
{ id: 'u-stefe', name: 'Person STEFE', type: 'person', meta: { fullName: "Stefan E.", email: "stefe@example.com", phone: "+49 123 456", duSince: "2020" } },
|
|
{ id: 'u-chfi', name: 'Person CHFI', type: 'person', meta: { fullName: "Christoph F.", email: "chfi@example.com", phone: "98765", duSince: "2024" } },
|
|
{ id: 'u-vendor-x', name: 'Person VENDOR-X', type: 'person', meta: { fullName: "Hr. Müller (Vendor X)", email: "sales@vendor-x.com", phone: "", duSince: "" } },
|
|
{ id: 'u-chrkl', name: 'Person ChrKl', type: 'person', meta: { ...tplPerson, fullName: "Christian Kl." } }
|
|
],
|
|
ratings: {},
|
|
topics: [
|
|
{ id: 201, contextId: 'daily-log', title: "Anruf: Lizenzprobleme Vendor X", processedInCurrentSession: true, idCollapsed: true, snoozeUntil: null, history: [{ date: today, text: "Hr. Müller angerufen. Lizenzserver ist down.\n- Ersatzticket erstellt: #INC-999\n- Eskalation an -> VENDOR-X\n- Info an Team -> STEFE\n- @VENDOR-X war sehr hilfsbereit bei der Umgehung." }] },
|
|
{ id: 202, contextId: 'daily-log', title: "Idee: Cloud-Backup Optimierung", processedInCurrentSession: false, idCollapsed: false, snoozeUntil: null, history: [{ date: yesterday, text: "Kosten laufen aus dem Ruder.\n- Idee: S3 Glacier Deep Archive nutzen?\n- Prüfen -> CHFI @P:CLOUD-MIGRATION\n- @CHFI hat super Ideen geliefert." }] },
|
|
{ id: 203, contextId: 'daily-log', title: "Quick-Fix: Drucker Buchhaltung", processedInCurrentSession: true, idCollapsed: true, snoozeUntil: null, history: [{ date: today, text: "Papierstau behoben. Toner bestellt." }] },
|
|
{ id: 204, contextId: 'daily-log', title: "Personalgespräch Vorbereitung", processedInCurrentSession: false, idCollapsed: false, snoozeUntil: null, history: [{ date: lastWeek, text: "- Ziele für Q1 prüfen\n- Schulungsbedarf klären\n- -> CHFI" }] },
|
|
{ id: 1, contextId: 'jf-sysadmins', title: "TISAX: Sperren Produktionsrechner", isNew: false, processedInCurrentSession: false, idCollapsed: false, snoozeUntil: null, history: [{ date: today, text: "- Entscheidung: Keine Ausnahmen mehr.\n- Umsetzung startet nächste Woche @P:TISAX" }, { date: lastWeek, text: "- Maßnahmen definiert:\n - Benutzergruppe für kein Internet -> STEFE @P:TISAX\n - VLAN „shared“ nutzen -> PHILO\n - IP-Range prüfen" }] },
|
|
{ id: 2, contextId: 'jf-sysadmins', title: "Greenbone-Prozess", processedInCurrentSession: false, idCollapsed: true, snoozeUntil: null, history: [{ date: yesterday, text: "Priorisierung nötig -> ERAY @P:Security" }] },
|
|
{ id: 101, contextId: 'jf-devs', title: "API Refactoring", processedInCurrentSession: false, idCollapsed: false, snoozeUntil: null, history: [{ date: today, text: "Neues Schema prüfen @P:TISAX\n- @ChrKl hat das verbockt." }] }
|
|
]
|
|
};
|
|
|
|
let currentContextId = 'daily-log';
|
|
let currentMode = 'prep';
|
|
let viewMode = 'agenda';
|
|
let draggedId = null;
|
|
let activeRatingContext = null;
|
|
let allCollapsed = false;
|
|
|
|
// --- INITIALIZATION ---
|
|
function init() {
|
|
ensureContextsExist();
|
|
renderSidebar();
|
|
loadContext(currentContextId);
|
|
}
|
|
|
|
function ensureContextsExist() {
|
|
let foundProjects = new Set(), foundPersons = new Set();
|
|
appData.topics.forEach(t => {
|
|
t.history.forEach(h => {
|
|
extractProjects(h.text).forEach(p => foundProjects.add(p));
|
|
extractAssignments(h.text).forEach(p => foundPersons.add(p));
|
|
extractMentions(h.text).forEach(p => foundPersons.add(p));
|
|
});
|
|
});
|
|
foundProjects.forEach(pName => { if (!appData.contexts.find(c => c.type === 'project' && c.name === 'Project ' + pName)) appData.contexts.push({ id: 'p-' + pName.toLowerCase(), name: 'Project ' + pName, type: 'project', meta: { ...tplProject } }); });
|
|
foundPersons.forEach(pName => { if (!appData.contexts.find(c => c.type === 'person' && c.name === 'Person ' + pName)) appData.contexts.push({ id: 'u-' + pName.toLowerCase(), name: 'Person ' + pName, type: 'person', meta: { ...tplPerson, fullName: pName } }); });
|
|
}
|
|
|
|
// --- RATING LOGIC ---
|
|
function openRating(topicId, histIdx, personName) {
|
|
activeRatingContext = { topicId, histIdx, personName };
|
|
document.getElementById('rating-title').innerText = `Rate interaction with @${personName}`;
|
|
document.getElementById('rating-modal').style.display = 'block';
|
|
}
|
|
|
|
function closeRating() { document.getElementById('rating-modal').style.display = 'none'; activeRatingContext = null; }
|
|
|
|
function submitRating(val) {
|
|
if (!activeRatingContext) return;
|
|
const key = `${activeRatingContext.topicId}-${activeRatingContext.histIdx}-${activeRatingContext.personName}`;
|
|
appData.ratings[key] = val;
|
|
closeRating();
|
|
renderAgenda();
|
|
if (viewMode === 'dashboard') loadContext(currentContextId);
|
|
}
|
|
|
|
// --- PARSERS ---
|
|
function extractAssignments(text) { const regex = /->\s*([\w]+)/g; let a = []; let m; while ((m = regex.exec(text)) !== null) a.push(m[1]); return a; }
|
|
function extractProjects(text) { const regex = /@P:([\w-]+)/g; let p = []; let m; while ((m = regex.exec(text)) !== null) p.push(m[1]); return p; }
|
|
function extractMentions(text) { const regex = /@(?!(?:P:))([\w-]+)/g; let p = []; let m; while ((m = regex.exec(text)) !== null) p.push(m[1]); return p; }
|
|
|
|
function parseToNestedList(text, topicId, histIdx) {
|
|
if (!text) return '';
|
|
const l = text.split('\n');
|
|
const n = l.map(x => {
|
|
const s = x.search(/\S|$/);
|
|
return { l: s, c: highlightTags(escapeHtml(x.trim().replace(/^[-*]\s+/, '')), topicId, histIdx), is: /^\s*[-*]/.test(x), ch: [] };
|
|
});
|
|
if (!n.some(x => x.is)) { return `<div class="list-content">${highlightTags(escapeHtml(text).replace(/\n/g, '<br>'), topicId, histIdx)}</div>`; }
|
|
let r = { l: -1, ch: [] }; let p = [r];
|
|
n.forEach(x => { if (!x.c) return; while (p.length > 1 && p[p.length - 1].l >= x.l) p.pop(); p[p.length - 1].ch.push(x); p.push(x) });
|
|
return rTree(r.ch);
|
|
}
|
|
|
|
function rTree(n) { if (!n || !n.length) return ''; let h = '<ul class="tree-list">'; n.forEach(x => { const c = x.ch.length > 0; h += `<li class="${c ? 'expanded-list' : ''}">${c ? '<span class="toggle-btn" onclick="tList(this)"></span>' : ''}<span class="list-content">${x.c}</span>${rTree(x.ch)}</li>` }); h += '</ul>'; return h; }
|
|
|
|
function highlightTags(t, topicId, histIdx) {
|
|
t = t.replace(/->\s*([\w]+)/g, '<span class="arrow-highlight">→</span> <span class="assignment-tag">$1</span>');
|
|
t = t.replace(/@P:([\w-]+)/g, '<span class="project-tag">@P:$1</span>');
|
|
if (topicId !== undefined && histIdx !== undefined) {
|
|
t = t.replace(/@(?!(?:P:))([\w-]+)/g, (match, p1) => {
|
|
const key = `${topicId}-${histIdx}-${p1}`;
|
|
const rating = appData.ratings[key];
|
|
const ratingClass = rating ? `rating-${rating}` : '';
|
|
return `<span class="person-ref-tag ${ratingClass}" onclick="openRating(${topicId}, ${histIdx}, '${p1}')">@${p1}</span>`;
|
|
});
|
|
}
|
|
return t;
|
|
}
|
|
|
|
function escapeHtml(t) { return t ? t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") : ''; }
|
|
window.tList = function (b) { const l = b.parentElement; if (l.classList.contains('expanded-list')) { l.classList.remove('expanded-list'); l.classList.add('collapsed-list'); l.querySelector('ul').style.display = 'none'; } else { l.classList.remove('collapsed-list'); l.classList.add('expanded-list'); l.querySelector('ul').style.display = 'block'; } };
|
|
|
|
// --- VIEW LOGIC (Restored) ---
|
|
function setMode(m) { currentMode = m; document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active', 'meeting-mode')); const x = document.getElementById(m === 'prep' ? 'btn-prep' : 'btn-meet'); if (x) { x.classList.add('active'); if (m === 'meeting') x.classList.add('meeting-mode'); } renderAgenda(); }
|
|
|
|
function switchView(vm) {
|
|
viewMode = vm;
|
|
document.querySelectorAll('.view-section').forEach(e => e.classList.remove('active'));
|
|
document.querySelectorAll('.tab-btn').forEach(e => e.classList.remove('active'));
|
|
document.getElementById(`view-${vm}`).classList.add('active');
|
|
const tb = document.getElementById(`tab-${vm.replace('dashboard', 'dashboard')}`);
|
|
if (tb) tb.classList.add('active');
|
|
const compBtn = document.getElementById('btn-compact');
|
|
if (compBtn) compBtn.style.display = (vm === 'agenda' ? 'block' : 'none');
|
|
if (vm === 'agenda') renderAgenda();
|
|
if (vm === 'journal') renderJournal();
|
|
if (vm === 'persons') renderPersons();
|
|
if (vm === 'snoozed') renderSnoozed();
|
|
}
|
|
|
|
// --- RENDERERS ---
|
|
function renderAgenda() {
|
|
if (viewMode !== 'agenda') return;
|
|
const c1 = document.getElementById('agendaList'); const c2 = document.getElementById('processedList');
|
|
if (!c1 || !c2) return;
|
|
c1.innerHTML = ''; c2.innerHTML = '';
|
|
|
|
const contextTopics = appData.topics.filter(t => t.contextId === currentContextId);
|
|
let openTopics = contextTopics.filter(t => !t.snoozeUntil && !t.processedInCurrentSession);
|
|
let processedTopics = contextTopics.filter(t => t.processedInCurrentSession);
|
|
|
|
let showOpen = currentMode === 'meeting' || currentContextId === 'daily-log';
|
|
let showProcessed = (currentMode === 'meeting' && processedTopics.length > 0);
|
|
|
|
document.getElementById('agenda-open-header').style.display = showOpen ? 'block' : 'none';
|
|
document.getElementById('agenda-processed-header').style.display = showProcessed ? 'block' : 'none';
|
|
|
|
if (currentMode !== 'meeting' && currentContextId !== 'daily-log') { if (processedTopics.length > 0) openTopics = [...openTopics, ...processedTopics]; processedTopics = []; }
|
|
|
|
openTopics.forEach(t => c1.appendChild(createTopicCard(t, false)));
|
|
processedTopics.forEach(t => c2.appendChild(createTopicCard(t, true)));
|
|
}
|
|
|
|
function createTopicCard(topic, isProcessed) {
|
|
const card = document.createElement('div');
|
|
const collapsedClass = topic.idCollapsed ? 'collapsed' : '';
|
|
card.className = `topic-card ${topic.isNew ? 'status-new' : ''} ${isProcessed ? 'status-processed' : ''} ${collapsedClass}`;
|
|
card.id = `card-${topic.id}`;
|
|
|
|
const isDraggable = !isProcessed && !topic.snoozeUntil;
|
|
if (isDraggable) { card.setAttribute('draggable', 'true'); card.ondragstart = (e) => handleDragStart(e, topic.id); card.ondragend = handleDragEnd; card.ondragover = handleDragOver; card.ondragleave = handleDragLeave; card.ondrop = (e) => handleDrop(e, topic.id); }
|
|
|
|
let badge = topic.isNew ? `<span class="new-badge">NEU</span>` : ''; if (isProcessed) badge += `<span class="processed-badge">BESPROCHEN/GEMERKT</span>`;
|
|
let controls = '';
|
|
if (currentContextId === 'daily-log') {
|
|
controls = `<div class="meeting-controls"><button class="btn-success" onclick="actionKeepOpen(${topic.id})">💬 Notieren</button><button class="btn-primary" onclick="actionDone(${topic.id})">✅ Erledigt / Archivieren</button><button class="btn-warning" onclick="actionSnooze(${topic.id})">zzz Später</button></div>`;
|
|
} else {
|
|
if (currentMode === 'meeting' && !isProcessed) { controls = `<div class="meeting-controls"><button class="btn-success" onclick="actionKeepOpen(${topic.id})">💬 Notieren & Offen</button><button class="btn-warning" onclick="actionSnooze(${topic.id})">zzz Verschieben</button><button class="btn-info" onclick="actionSkip(${topic.id})">⏭️ Überspringen</button><button class="btn-danger" onclick="actionDone(${topic.id})">✅ Erledigt</button></div>`; }
|
|
else if (isProcessed || currentMode === 'prep') { controls = `<div class="meeting-controls"><button class="btn-success" onclick="addNoteOnly(${topic.id})">💾 Speichern</button><button class="btn-danger" style="margin-left:auto" onclick="actionDone(${topic.id})">Archivieren</button></div>`; }
|
|
}
|
|
|
|
const historyHtml = topic.history.map((n, idx) => `<div class="history-item" id="hist-${topic.id}-${idx}"><div class="history-header"><span class="history-date">[${n.date}]</span><button class="edit-history-btn" onclick="editHistory(${topic.id}, ${idx})">✎</button></div><div class="history-content-viewer">${parseToNestedList(n.text, topic.id, idx)}</div></div>`).reverse().join('');
|
|
card.innerHTML = `<div class="topic-header" onclick="toggleTopicCard(${topic.id})" draggable="false"><div class="topic-title"><span class="collapse-icon">▼</span><span id="title-text-${topic.id}">${topic.title}</span><button class="title-edit-btn" onclick="event.stopPropagation(); editTitle(${topic.id})">✎</button>${badge}</div></div><div class="topic-body"><div class="history">${historyHtml}</div><div class="add-note-area" style="display:flex; flex-direction:column; gap:5px; margin-top:10px;"><textarea id="note-input-${topic.id}" placeholder="- Notiz... (z.B. '-> NAME' or '@P:PROJECT')" onclick="event.stopPropagation()"></textarea>${controls}</div></div>`;
|
|
return card;
|
|
}
|
|
|
|
function renderDashboardContent(name, type) {
|
|
const c = document.getElementById('dashboardContent'); c.innerHTML = '';
|
|
let re = [];
|
|
appData.topics.forEach(t => {
|
|
t.history.forEach((n, idx) => { // Use idx for rating key
|
|
let matches = false, isRef = false;
|
|
if (type === 'project') matches = extractProjects(n.text).includes(name);
|
|
else {
|
|
matches = extractAssignments(n.text).includes(name);
|
|
if (!matches) {
|
|
// Check for @Mention references
|
|
if (extractMentions(n.text).includes(name)) { matches = true; isRef = true; }
|
|
}
|
|
}
|
|
|
|
if (matches) {
|
|
// Retrieve Rating if person mention
|
|
let ratingInfo = '';
|
|
if (type === 'person') {
|
|
const key = `${t.id}-${idx}-${name}`;
|
|
const r = appData.ratings[key];
|
|
if (r) {
|
|
const labels = ["Ganz Schlecht", "Schlecht", "Gut", "Sehr Gut"];
|
|
const colors = ["#d63031", "#e17055", "#00b894", "#0984e3"];
|
|
ratingInfo = `<span style="float:right; font-size:0.8em; padding:2px 6px; border-radius:4px; margin-left:10px; background:${colors[r - 1]}; color:white;">Rating: ${labels[r - 1]}</span>`;
|
|
}
|
|
}
|
|
re.push({ tt: t.title, cn: appData.contexts.find(x => x.id === t.contextId)?.name || 'Unknown', d: n.date, x: n.text, tid: t.id, hidx: idx, ri: ratingInfo });
|
|
}
|
|
});
|
|
});
|
|
if (!re.length) { c.innerHTML = '<div style="color:#888;">No entries found.</div>'; return; }
|
|
re.sort((a, b) => new Date(b.d) - new Date(a.d));
|
|
let h = ''; re.forEach(e => { h += `<div class="person-task"><div class="person-task-ref">${e.d} in <span style="color:var(--accent)">${e.cn}</span>${e.ri}: ${e.tt}</div>${parseToNestedList(e.x, e.tid, e.hidx)}</div>`; });
|
|
c.innerHTML = `<div class="dashboard-view-card"><div class="dashboard-view-header">Activity Log for ${name}</div>${h}</div>`;
|
|
}
|
|
|
|
// --- METADATA EDITORS ---
|
|
function renderProjectMeta(ctx) { const c = document.getElementById('metaEditorContainer'); const m = ctx.meta || { ...tplProject }; c.innerHTML = `<div class="meta-editor"><h3 style="margin-top:0; color:#aaa; border-bottom:1px solid #444; padding-bottom:5px;">📋 Projekt Metadaten</h3><div class="meta-field"><label>Status:</label><input type="text" value="${m.status}" onchange="updMeta('${ctx.id}','status',this.value)"></div><div class="meta-field"><label>Verantwortlich:</label><input type="text" value="${m.owner}" onchange="updMeta('${ctx.id}','owner',this.value)"></div><div class="meta-field"><label>Links:</label><input type="text" value="${m.links}" onchange="updMeta('${ctx.id}','links',this.value)"></div></div>`; }
|
|
function renderPersonMeta(ctx) { const c = document.getElementById('metaEditorContainer'); const m = ctx.meta || { ...tplPerson }; c.innerHTML = `<div class="meta-editor"><h3 style="margin-top:0; color:#aaa; border-bottom:1px solid #444; padding-bottom:5px;">👤 Person Metadaten</h3><div class="meta-field"><label>Voller Name:</label><input type="text" value="${m.fullName}" onchange="updMeta('${ctx.id}','fullName',this.value)"></div><div class="meta-field"><label>Email (📨):</label><input type="text" value="${m.email}" onchange="updMeta('${ctx.id}','email',this.value)"></div><div class="meta-field"><label>Telefon (☎️):</label><input type="text" value="${m.phone}" onchange="updMeta('${ctx.id}','phone',this.value)"></div><div class="meta-field"><label>Per Du seit:</label><input type="text" value="${m.duSince}" onchange="updMeta('${ctx.id}','duSince',this.value)"></div></div>`; }
|
|
window.updMeta = function (cid, f, v) { const c = appData.contexts.find(x => x.id === cid); if (c) { if (!c.meta) c.meta = {}; c.meta[f] = v; } };
|
|
|
|
// --- HELPERS & ACTIONS ---
|
|
function renderSidebar() {
|
|
const genList = document.getElementById('nav-general'); const ctxList = document.getElementById('nav-contexts'); const prjList = document.getElementById('nav-projects'); const pplList = document.getElementById('nav-people');
|
|
genList.innerHTML = ''; ctxList.innerHTML = ''; prjList.innerHTML = ''; pplList.innerHTML = '';
|
|
const dailyLog = appData.contexts.find(c => c.id === 'daily-log');
|
|
if (dailyLog) { const li = document.createElement('li'); li.className = `nav-item ${dailyLog.id === currentContextId ? 'active' : ''}`; li.innerText = `${dailyLog.name}`; li.onclick = () => loadContext(dailyLog.id); genList.appendChild(li); }
|
|
appData.contexts.filter(c => c.type === 'meeting' && c.id !== 'daily-log').forEach(c => { const li = document.createElement('li'); li.className = `nav-item ${c.id === currentContextId ? 'active' : ''}`; li.innerText = `📅 ${c.name}`; li.onclick = () => loadContext(c.id); ctxList.appendChild(li); });
|
|
appData.contexts.filter(c => c.type === 'project').forEach(c => { const pName = c.name.replace('Project ', ''); const isActive = currentContextId === c.id; const li = document.createElement('li'); li.className = `nav-item ${isActive ? 'active' : ''}`; li.innerText = `🚀 ${pName}`; li.onclick = () => loadContext(c.id); prjList.appendChild(li); });
|
|
appData.contexts.filter(c => c.type === 'person').forEach(c => { const pName = c.name.replace('Person ', ''); const isActive = currentContextId === c.id; const li = document.createElement('li'); li.className = `nav-item ${isActive ? 'active' : ''}`; li.innerText = `👤 ${pName}`; li.onclick = () => loadContext(c.id); pplList.appendChild(li); });
|
|
}
|
|
function loadContext(contextId) {
|
|
currentContextId = contextId; const ctx = appData.contexts.find(c => c.id === contextId);
|
|
if (ctx.type === 'meeting') {
|
|
document.getElementById('context-title').innerText = ctx.name;
|
|
if (ctx.id === 'daily-log') { document.getElementById('mode-switch-container').style.display = 'none'; currentMode = 'meeting'; }
|
|
else { document.getElementById('mode-switch-container').style.display = 'flex'; }
|
|
document.getElementById('tab-agenda').style.display = 'inline-block'; document.getElementById('tab-journal').style.display = 'inline-block'; document.getElementById('tab-persons').style.display = 'inline-block'; document.getElementById('tab-dashboard').style.display = 'none'; switchView('agenda');
|
|
} else {
|
|
const pName = ctx.name.replace(ctx.type === 'project' ? 'Project ' : 'Person ', ''); document.getElementById('context-title').innerText = ctx.name; document.getElementById('mode-switch-container').style.display = 'none'; document.getElementById('tab-agenda').style.display = 'none'; document.getElementById('tab-dashboard').style.display = 'inline-block'; document.getElementById('tab-dashboard').innerText = `📊 Dashboard: ${pName}`; switchView('dashboard');
|
|
if (ctx.type === 'project') { renderProjectMeta(ctx); renderDashboardContent(pName, 'project'); } else { renderPersonMeta(ctx); renderDashboardContent(pName, 'person'); }
|
|
}
|
|
renderSidebar();
|
|
}
|
|
function renderJournal() { const c = document.getElementById('journalList'); c.innerHTML = ''; const ct = appData.topics.filter(t => t.contextId === currentContextId); let an = []; ct.forEach(t => { t.history.forEach((h, idx) => { an.push({ ...h, title: t.title, tid: t.id, hidx: idx }) }) }); an.sort((a, b) => new Date(b.date) - new Date(a.date)); let cd = null; let dc = null; an.forEach(n => { if (n.date !== cd) { cd = n.date; dc = document.createElement('div'); dc.className = 'journal-day'; dc.innerHTML = `<div class="journal-date-header">${cd}</div>`; c.appendChild(dc); } const e = document.createElement('div'); e.className = 'journal-entry'; e.innerHTML = `<span class="journal-topic-ref">${n.title}</span>${parseToNestedList(n.text, n.tid, n.hidx)}`; dc.appendChild(e); }); }
|
|
function renderPersons() { const c = document.getElementById('personsList'); c.innerHTML = ''; const ct = appData.topics.filter(t => t.contextId === currentContextId); let a = {}; ct.forEach(t => { t.history.forEach(n => { extractAssignments(n.text).forEach(p => { if (!a[p]) a[p] = []; a[p].push({ tt: t.title, d: n.date, l: n.text }) }) }) }); if (!Object.keys(a).length) { c.innerHTML = '<div style="color:#888;">Keine Aufgaben.</div>'; return; } Object.keys(a).sort().forEach(p => { const k = document.createElement('div'); k.className = 'person-card'; k.innerHTML = `<div class="person-header">👤 ${p}</div>` + a[p].map(t => `<div class="person-task"><div class="person-task-ref">${t.d} - ${t.tt}</div>${parseToNestedList(t.l)}</div>`).join(''); c.appendChild(k); }); }
|
|
function renderSnoozed() { const c = document.getElementById('snoozedList'); const s = appData.topics.filter(t => t.contextId === currentContextId && t.snoozeUntil); c.innerHTML = s.length ? '' : '<div style="color:#888;text-align:center">Leer.</div>'; s.forEach(t => { const k = document.createElement('div'); k.className = 'topic-card status-snoozed'; k.innerHTML = `<div class="topic-header"><span class="topic-title">${t.title}</span><button class="btn-success" onclick="unsnooze(${t.id})">Reaktivieren</button></div><div style="padding:15px;color:#aaa">Bis: ${t.snoozeUntil}</div>`; c.appendChild(k); }); }
|
|
|
|
function getNoteInput(id) { const el = document.getElementById(`note-input-${id}`); return el ? el.value.trim() : ""; }
|
|
function addTopic() { const input = document.getElementById('newTopicInput'); if (!input.value.trim()) return; appData.topics.unshift({ id: Date.now(), contextId: currentContextId, title: input.value, isNew: true, snoozeUntil: null, processedInCurrentSession: false, idCollapsed: false, history: [{ date: today, text: "Thema angelegt." }] }); input.value = ''; renderAgenda(); }
|
|
function saveNote(id) {
|
|
const text = getNoteInput(id);
|
|
const topic = appData.topics.find(t => t.id === id);
|
|
if (text && topic) {
|
|
topic.history.push({ date: today, text: text });
|
|
topic.isNew = false;
|
|
extractProjects(text).forEach(p => { if (!appData.contexts.find(c => c.type === 'project' && c.name === 'Project ' + p)) appData.contexts.push({ id: 'p-' + p.toLowerCase(), name: 'Project ' + p, type: 'project', meta: { ...tplProject } }); });
|
|
extractAssignments(text).forEach(p => { if (!appData.contexts.find(c => c.type === 'person' && c.name === 'Person ' + p)) appData.contexts.push({ id: 'u-' + p.toLowerCase(), name: 'Person ' + p, type: 'person', meta: { ...tplPerson, fullName: p } }); });
|
|
extractMentions(text).forEach(p => { if (!appData.contexts.find(c => c.type === 'person' && c.name === 'Person ' + p)) appData.contexts.push({ id: 'u-' + p.toLowerCase(), name: 'Person ' + p, type: 'person', meta: { ...tplPerson, fullName: p } }); });
|
|
renderSidebar();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function actionKeepOpen(id) { saveNote(id); const t = appData.topics.find(x => x.id === id); t.processedInCurrentSession = true; t.isNew = false; renderAgenda(); }
|
|
function actionDone(id) { if (confirm("Archivieren?")) { saveNote(id); appData.topics = appData.topics.filter(x => x.id !== id); renderAgenda(); } }
|
|
function actionSkip(id) { const t = appData.topics.find(x => x.id === id); t.processedInCurrentSession = true; renderAgenda(); }
|
|
function actionSnooze(id) { const i = prompt("Datum:", "2026-06-01"); if (i) { saveNote(id); const t = appData.topics.find(x => x.id === id); t.snoozeUntil = i; t.processedInCurrentSession = true; renderAgenda(); } }
|
|
function unsnooze(id) { const t = appData.topics.find(x => x.id === id); t.snoozeUntil = null; t.isNew = false; renderSnoozed(); renderAgenda(); }
|
|
function addNoteOnly(id) { if (saveNote(id)) renderAgenda(); }
|
|
function toggleAllCompact() { allCollapsed = !allCollapsed; appData.topics.forEach(t => t.idCollapsed = allCollapsed); renderAgenda(); }
|
|
function toggleTopicCard(id) { const t = appData.topics.find(x => x.id === id); t.idCollapsed = !t.idCollapsed; renderAgenda(); }
|
|
function editHistory(id, idx) { const c = document.getElementById(`hist-${id}-${idx}`); const t = appData.topics.find(x => x.id === id); if (c) c.innerHTML = `<div class="edit-mode"><textarea class="edit-area" id="edit-area-${id}-${idx}">${t.history[idx].text}</textarea><div class="edit-actions"><button class="btn-success" onclick="saveHistoryEdit(${id},${idx})">Speichern</button></div></div>`; }
|
|
function saveHistoryEdit(id, idx) { const t = appData.topics.find(x => x.id === id); const newText = document.getElementById(`edit-area-${id}-${idx}`).value; t.history[idx].text = newText; extractProjects(newText).forEach(p => { if (!appData.contexts.find(c => c.type === 'project' && c.name === 'Project ' + p)) appData.contexts.push({ id: 'p-' + p.toLowerCase(), name: 'Project ' + p, type: 'project', meta: { ...tplProject } }); }); extractAssignments(newText).forEach(p => { if (!appData.contexts.find(c => c.type === 'person' && c.name === 'Person ' + p)) appData.contexts.push({ id: 'u-' + p.toLowerCase(), name: 'Person ' + p, type: 'person', meta: { ...tplPerson, fullName: p } }); }); extractMentions(newText).forEach(p => { if (!appData.contexts.find(c => c.type === 'person' && c.name === 'Person ' + p)) appData.contexts.push({ id: 'u-' + p.toLowerCase(), name: 'Person ' + p, type: 'person', meta: { ...tplPerson, fullName: p } }); }); renderAgenda(); init(); }
|
|
function editTitle(id) { const t = appData.topics.find(x => x.id === id); const n = prompt("Titel", t.title); if (n) { t.title = n; renderAgenda(); } }
|
|
function handleTopicEnter(e) { if (e.key === 'Enter') addTopic(); }
|
|
function handleDragStart(e, id) { draggedId = id; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', id); setTimeout(() => e.target.classList.add('dragging'), 0); }
|
|
function handleDragEnd(e) { e.target.classList.remove('dragging'); document.querySelectorAll('.topic-card').forEach(el => el.classList.remove('drag-over-top', 'drag-over-bottom')); draggedId = null; }
|
|
function handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const card = e.target.closest('.topic-card'); if (!card || card.classList.contains('dragging')) return; const rect = card.getBoundingClientRect(); const offset = e.clientY - rect.top; if (offset < rect.height / 2) { card.classList.add('drag-over-top'); card.classList.remove('drag-over-bottom'); } else { card.classList.add('drag-over-bottom'); card.classList.remove('drag-over-top'); } }
|
|
function handleDragLeave(e) { const card = e.target.closest('.topic-card'); if (card) card.classList.remove('drag-over-top', 'drag-over-bottom'); }
|
|
function handleDrop(e, targetId) { e.preventDefault(); const card = e.target.closest('.topic-card'); if (!card) return; const draggingBefore = card.classList.contains('drag-over-top'); card.classList.remove('drag-over-top', 'drag-over-bottom'); if (draggedId === targetId || !draggedId) return; const fromIndex = appData.topics.findIndex(t => t.id === draggedId); const toIndex = appData.topics.findIndex(t => t.id === targetId); if (fromIndex < 0 || toIndex < 0) return; const [movedItem] = appData.topics.splice(fromIndex, 1); let newToIndex = appData.topics.findIndex(t => t.id === targetId); if (draggingBefore) appData.topics.splice(newToIndex, 0, movedItem); else appData.topics.splice(newToIndex + 1, 0, movedItem); renderAgenda(); }
|
|
|
|
init();
|
|
</script>
|
|
</body>
|
|
|
|
</html> |