UI-Anpassungen
Dieser Commit ist enthalten in:
committet von
Server Deploy
Ursprung
7d67557be4
Commit
ef153789cc
@ -156,36 +156,99 @@
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
/* View Tabs */
|
||||
/* View Tabs - Modern Design */
|
||||
.view-tabs {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
background: var(--bg-tertiary);
|
||||
gap: var(--spacing-1);
|
||||
padding: 8px 12px;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-radius: var(--radius-lg);
|
||||
position: relative;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.view-tab {
|
||||
padding: 6px 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
padding: 12px 20px;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-tertiary);
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
border-radius: 0;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
transition: all var(--transition-default);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.view-tab:hover {
|
||||
color: var(--text-secondary);
|
||||
/* Tab Icon */
|
||||
.view-tab svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
transition: all var(--transition-default);
|
||||
}
|
||||
|
||||
.view-tab.active {
|
||||
/* Hover State */
|
||||
.view-tab:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-card);
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.view-tab:hover svg {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Active State with Underline */
|
||||
.view-tab.active {
|
||||
color: var(--primary);
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.view-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: var(--primary);
|
||||
border-radius: 3px 3px 0 0;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Animation for active indicator */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Subtle border between tabs */
|
||||
.view-tab:not(:last-child)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 25%;
|
||||
bottom: 25%;
|
||||
width: 1px;
|
||||
background: var(--border-light);
|
||||
opacity: 0.5;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.view-tab:hover::before,
|
||||
.view-tab:hover + .view-tab::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
@ -691,12 +691,16 @@
|
||||
position: fixed;
|
||||
min-width: 280px;
|
||||
max-width: 350px;
|
||||
max-height: 80vh;
|
||||
padding: var(--spacing-4);
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: var(--z-dropdown);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calendar-day-detail-header {
|
||||
@ -717,8 +721,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
max-height: 300px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.calendar-detail-task {
|
||||
|
||||
@ -1,9 +1,29 @@
|
||||
/**
|
||||
* TASKMATE - Contacts Styles
|
||||
* ==========================
|
||||
* Kartenansicht für Kontakte
|
||||
* Tabellenansicht für Kontakte mit erweiterten Funktionen
|
||||
*/
|
||||
|
||||
/* =============================================================================
|
||||
VIEW CONTAINER
|
||||
============================================================================= */
|
||||
|
||||
.view-contacts {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.view-contacts .view-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-6);
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
HEADER & CONTROLS
|
||||
============================================================================= */
|
||||
@ -12,165 +32,299 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-md);
|
||||
margin-bottom: var(--spacing-6);
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.contacts-controls {
|
||||
.contacts-header h2 {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
gap: var(--spacing-3);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.contacts-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
CONTROLS BAR
|
||||
============================================================================= */
|
||||
|
||||
.contacts-controls {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--spacing-4);
|
||||
margin-bottom: var(--spacing-5);
|
||||
}
|
||||
|
||||
.contacts-controls-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.bulk-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-3);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bulk-actions.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bulk-actions-info {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.contacts-filters {
|
||||
display: flex;
|
||||
gap: var(--space-xs);
|
||||
gap: var(--spacing-4);
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
GRID LAYOUT
|
||||
============================================================================= */
|
||||
|
||||
.contacts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: var(--space-md);
|
||||
margin-top: var(--space-md);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
CONTACT CARD
|
||||
============================================================================= */
|
||||
|
||||
.contact-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--space-md);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
.filter-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.contact-card:hover {
|
||||
.filter-select {
|
||||
min-width: 180px;
|
||||
height: 36px;
|
||||
padding: 0 var(--spacing-4);
|
||||
padding-right: 36px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--spacing-2) center;
|
||||
background-size: 14px;
|
||||
}
|
||||
|
||||
.filter-select:hover {
|
||||
border-color: var(--border-dark);
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
border-color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-md);
|
||||
box-shadow: var(--shadow-focus);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.contact-card-header {
|
||||
/* =============================================================================
|
||||
TABLE LAYOUT
|
||||
============================================================================= */
|
||||
|
||||
.contacts-table-container {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contacts-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.contacts-table th,
|
||||
.contacts-table td {
|
||||
padding: var(--spacing-3) var(--spacing-4);
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.contacts-table th {
|
||||
background: var(--bg-secondary);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-sm);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
border-bottom: 2px solid var(--border-default);
|
||||
}
|
||||
|
||||
.contacts-table th:first-child {
|
||||
padding-left: var(--spacing-4);
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.contacts-table th:last-child {
|
||||
padding-right: var(--spacing-4);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Sortable headers */
|
||||
.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.sortable:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.sortable::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.sortable.sort-asc::after {
|
||||
border-bottom: 6px solid var(--primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sortable.sort-desc::after {
|
||||
border-top: 6px solid var(--primary);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Table rows */
|
||||
.contacts-table tbody tr {
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.contacts-table tbody tr:hover {
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.contacts-table tbody tr.selected {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.contacts-table td {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.contacts-table td:first-child {
|
||||
padding-left: var(--spacing-4);
|
||||
}
|
||||
|
||||
.contacts-table td:last-child {
|
||||
padding-right: var(--spacing-4);
|
||||
}
|
||||
|
||||
/* Checkbox column */
|
||||
.checkbox-cell {
|
||||
width: 40px;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.table-checkbox {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
border: 2px solid var(--border-default);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-primary);
|
||||
position: relative;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.table-checkbox:hover {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.table-checkbox:checked {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.table-checkbox:checked::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Name column with avatar */
|
||||
.name-cell {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-sm);
|
||||
align-items: center;
|
||||
gap: var(--spacing-3);
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.contact-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
.contact-avatar-small {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
font-weight: var(--font-semibold);
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contact-actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.contact-card:hover .contact-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.contact-actions .btn-icon {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.contact-actions .btn-icon:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
CONTACT INFO
|
||||
============================================================================= */
|
||||
|
||||
.contact-card-body {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.contact-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--space-xs);
|
||||
.contact-name-link {
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-medium);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-company {
|
||||
font-size: 14px;
|
||||
.contact-name-link:hover {
|
||||
color: var(--primary);
|
||||
margin-bottom: 4px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.contact-position {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.contact-email,
|
||||
.contact-phone,
|
||||
.contact-mobile {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.contact-email i,
|
||||
.contact-phone i,
|
||||
.contact-mobile i {
|
||||
width: 14px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.contact-email:hover,
|
||||
.contact-phone:hover,
|
||||
.contact-mobile:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
TAGS
|
||||
============================================================================= */
|
||||
|
||||
.contact-tags {
|
||||
/* Tags cell */
|
||||
.tags-cell {
|
||||
display: flex;
|
||||
gap: var(--spacing-1);
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: var(--space-sm);
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.contact-tag {
|
||||
@ -178,8 +332,60 @@
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid var(--border-default);
|
||||
font-weight: var(--font-medium);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Actions column */
|
||||
.actions-cell {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-table-action {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-default);
|
||||
color: var(--text-secondary);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-table-action:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.btn-table-action svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Export button */
|
||||
.btn-export {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.btn-export svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
@ -188,27 +394,35 @@
|
||||
|
||||
.contacts-empty {
|
||||
text-align: center;
|
||||
padding: var(--space-xl) var(--space-md);
|
||||
padding: var(--spacing-8) var(--spacing-4);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-xl);
|
||||
border: 1px solid var(--border-default);
|
||||
max-width: 500px;
|
||||
margin: var(--spacing-8) auto;
|
||||
}
|
||||
|
||||
.contacts-empty i {
|
||||
font-size: 48px;
|
||||
.contacts-empty .empty-icon {
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: var(--space-md);
|
||||
margin-bottom: var(--spacing-4);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.contacts-empty .empty-icon svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.contacts-empty h3 {
|
||||
font-size: 20px;
|
||||
margin-bottom: var(--space-xs);
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
margin-bottom: var(--spacing-2);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.contacts-empty p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--space-md);
|
||||
margin-bottom: var(--spacing-4);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
@ -244,29 +458,105 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
PAGINATION
|
||||
============================================================================= */
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-4);
|
||||
border-top: 1px solid var(--border-default);
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-default);
|
||||
color: var(--text-secondary);
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(:disabled) {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.pagination-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
RESPONSIVE
|
||||
============================================================================= */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1200px) {
|
||||
.contacts-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.contacts-table {
|
||||
min-width: 900px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 968px) {
|
||||
.view-contacts .view-wrapper {
|
||||
padding: var(--spacing-4);
|
||||
}
|
||||
|
||||
.contacts-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.contacts-controls {
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.contacts-search {
|
||||
max-width: none;
|
||||
.contacts-controls {
|
||||
padding: var(--spacing-3);
|
||||
}
|
||||
|
||||
.contacts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.contacts-controls-top {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.bulk-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
min-width: 120px;
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.contacts-table th,
|
||||
.contacts-table td {
|
||||
padding: var(--spacing-2) var(--spacing-3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@ -280,4 +570,9 @@
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Mobile: Hide less important columns */
|
||||
.hide-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -112,11 +112,102 @@
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Advance Options */
|
||||
/* Time Input Styling - Match Date Input */
|
||||
#reminder-modal input[type="time"] {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
font-family: var(--font-primary);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-lg);
|
||||
transition: all var(--transition-fast);
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
#reminder-modal input[type="time"]:hover {
|
||||
border-color: var(--border-dark);
|
||||
}
|
||||
|
||||
#reminder-modal input[type="time"]:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: var(--shadow-focus);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Advance Options - Redesigned */
|
||||
.advance-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: var(--spacing-3);
|
||||
background: var(--bg-secondary);
|
||||
padding: var(--spacing-3);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
/* Reminder Advance Control */
|
||||
.reminder-advance-control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-2);
|
||||
}
|
||||
|
||||
.reminder-advance-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.reminder-number-input {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
padding: 10px 8px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
background: white;
|
||||
border: 2px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.reminder-number-input:focus {
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.reminder-unit-select {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
max-width: 180px;
|
||||
padding: 10px 16px;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
background: white;
|
||||
border: 2px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3e%3cpath d='M6 9l6 6 6-6'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
background-size: 16px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.reminder-unit-select:focus {
|
||||
border-color: var(--primary);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.reminder-advance-suffix {
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-semibold);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
@ -175,7 +266,7 @@
|
||||
}
|
||||
|
||||
/* =====================
|
||||
CUSTOM SELECT (USER DROPDOWN)
|
||||
CUSTOM SELECT (USER DROPDOWN) - Aligned with Multi-Select Design
|
||||
===================== */
|
||||
|
||||
.custom-select {
|
||||
@ -187,35 +278,38 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-primary);
|
||||
padding: 8px 14px;
|
||||
background: white;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: 6px;
|
||||
border-radius: var(--radius-lg);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
transition: all var(--transition-fast);
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.custom-select-trigger:hover {
|
||||
border-color: var(--border-hover);
|
||||
border-color: var(--border-dark);
|
||||
}
|
||||
|
||||
.custom-select-trigger.active {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.custom-select-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: var(--spacing-2);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.custom-select-arrow {
|
||||
color: var(--text-secondary);
|
||||
transition: transform 0.2s ease;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-tertiary);
|
||||
transition: transform var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.custom-select.open .custom-select-arrow {
|
||||
@ -227,52 +321,48 @@
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bg-card);
|
||||
margin-top: 4px;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 1000;
|
||||
max-height: 200px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
z-index: 1001;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.2s ease;
|
||||
min-width: 200px;
|
||||
display: none;
|
||||
/* Ensure opaque background */
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
.custom-select.open .custom-select-options {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.custom-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
gap: var(--spacing-3);
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.custom-select-option:last-child {
|
||||
border-bottom: none;
|
||||
transition: background-color var(--transition-fast);
|
||||
color: var(--text-primary);
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.custom-select-option:hover {
|
||||
background: var(--bg-hover);
|
||||
background-color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
.custom-select-option.selected {
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
background-color: #e0e7ff !important;
|
||||
}
|
||||
|
||||
.option-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border-radius: var(--radius-full);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -284,7 +374,8 @@
|
||||
|
||||
.option-text {
|
||||
font-size: 14px;
|
||||
color: #000000 !important;
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.selected-user-avatar {
|
||||
@ -325,11 +416,22 @@
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
#reminder-modal *:not(.btn):not(.custom-select-option):hover {
|
||||
#reminder-modal *:not(.btn):not(.custom-select-option):not(.option-avatar):not(.option-text):hover {
|
||||
background: transparent !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
/* Ensure dropdown options have solid background */
|
||||
#reminder-modal .custom-select-options {
|
||||
background-color: #ffffff !important;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* Keep text elements transparent but allow avatars to have background */
|
||||
#reminder-modal .custom-select-option .option-text {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Stelle sicher, dass Text im Modal immer lesbar bleibt */
|
||||
#reminder-modal .modal-content {
|
||||
background: var(--bg-card) !important;
|
||||
@ -395,10 +497,11 @@
|
||||
===================== */
|
||||
|
||||
/* Reminder Button in Calendar Toolbar */
|
||||
.btn-reminder {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||||
.btn-reminder,
|
||||
.btn-secondary.btn-reminder {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%) !important;
|
||||
color: white !important;
|
||||
border: 2px solid #ea580c;
|
||||
border: 2px solid #ea580c !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@ -413,10 +516,12 @@
|
||||
}
|
||||
|
||||
.btn-reminder:hover,
|
||||
.btn-reminder:focus {
|
||||
background: linear-gradient(135deg, #ea580c 0%, #c2410c 100%);
|
||||
.btn-reminder:focus,
|
||||
.btn-secondary.btn-reminder:hover,
|
||||
.btn-secondary.btn-reminder:focus {
|
||||
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%) !important;
|
||||
color: white !important;
|
||||
border-color: #c2410c;
|
||||
border-color: #dc2626 !important;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(234, 88, 12, 0.4);
|
||||
text-decoration: none;
|
||||
@ -475,6 +580,66 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Calendar Reminder Add Button - Orange styling */
|
||||
.calendar-week-add-reminder {
|
||||
margin: var(--spacing-2) var(--spacing-3) var(--spacing-3);
|
||||
padding: var(--spacing-2);
|
||||
background: #f97316;
|
||||
border: 1px solid #f97316;
|
||||
border-radius: var(--radius-lg);
|
||||
color: white !important;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.calendar-week-add-reminder:hover {
|
||||
background: #ea580c;
|
||||
border-color: #dc2626;
|
||||
color: white !important;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.4);
|
||||
}
|
||||
|
||||
/* Secondary reminder button - orange variant */
|
||||
.btn-reminder-secondary,
|
||||
.btn.btn-reminder-secondary {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%) !important;
|
||||
color: white !important;
|
||||
border: 2px solid #ea580c !important;
|
||||
font-weight: var(--font-medium);
|
||||
transition: all var(--transition-fast);
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
gap: 6px !important;
|
||||
}
|
||||
|
||||
.btn-reminder-secondary .icon,
|
||||
.btn.btn-reminder-secondary .icon {
|
||||
color: white !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
|
||||
.btn-reminder-secondary:hover,
|
||||
.btn-reminder-secondary:focus,
|
||||
.btn.btn-reminder-secondary:hover,
|
||||
.btn.btn-reminder-secondary:focus {
|
||||
background: linear-gradient(135deg, #ea580c 0%, #dc2626 100%) !important;
|
||||
color: white !important;
|
||||
border-color: #dc2626 !important;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 8px rgba(249, 115, 22, 0.4);
|
||||
}
|
||||
|
||||
.btn-reminder-secondary:active,
|
||||
.btn.btn-reminder-secondary:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 4px rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
/* Reminder Detail Popup */
|
||||
.calendar-reminder-detail {
|
||||
min-width: 300px;
|
||||
|
||||
@ -173,13 +173,67 @@
|
||||
<div class="header-center">
|
||||
<!-- View Tabs -->
|
||||
<nav class="view-tabs">
|
||||
<button class="view-tab active" data-view="board">Board</button>
|
||||
<button class="view-tab" data-view="list">Liste</button>
|
||||
<button class="view-tab" data-view="calendar">Kalender</button>
|
||||
<button class="view-tab" data-view="proposals">Genehmigung</button>
|
||||
<button class="view-tab" data-view="coding">Coding</button>
|
||||
<button class="view-tab" data-view="knowledge">Wissen</button>
|
||||
<button class="view-tab" data-view="contacts">Kontakte</button>
|
||||
<button class="view-tab active" data-view="board">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1"/>
|
||||
<rect x="14" y="3" width="7" height="7" rx="1"/>
|
||||
<rect x="3" y="14" width="7" height="7" rx="1"/>
|
||||
<rect x="14" y="14" width="7" height="7" rx="1"/>
|
||||
</svg>
|
||||
Board
|
||||
</button>
|
||||
<button class="view-tab" data-view="list">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="8" y1="6" x2="21" y2="6"/>
|
||||
<line x1="8" y1="12" x2="21" y2="12"/>
|
||||
<line x1="8" y1="18" x2="21" y2="18"/>
|
||||
<circle cx="3.5" cy="6" r="1" fill="currentColor"/>
|
||||
<circle cx="3.5" cy="12" r="1" fill="currentColor"/>
|
||||
<circle cx="3.5" cy="18" r="1" fill="currentColor"/>
|
||||
</svg>
|
||||
Liste
|
||||
</button>
|
||||
<button class="view-tab" data-view="calendar">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2"/>
|
||||
<line x1="16" y1="2" x2="16" y2="6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="6"/>
|
||||
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||
<circle cx="12" cy="16" r="1" fill="currentColor"/>
|
||||
</svg>
|
||||
Kalender
|
||||
</button>
|
||||
<button class="view-tab" data-view="proposals">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M9 11l3 3L22 4"/>
|
||||
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
|
||||
</svg>
|
||||
Genehmigung
|
||||
</button>
|
||||
<button class="view-tab" data-view="coding">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="16 18 22 12 16 6"/>
|
||||
<polyline points="8 6 2 12 8 18"/>
|
||||
<line x1="12" y1="20" x2="12" y2="4" transform="rotate(-15 12 12)"/>
|
||||
</svg>
|
||||
Coding
|
||||
</button>
|
||||
<button class="view-tab" data-view="knowledge">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
|
||||
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
|
||||
</svg>
|
||||
Wissen
|
||||
</button>
|
||||
<button class="view-tab" data-view="contacts">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||
</svg>
|
||||
Kontakte
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@ -579,42 +633,103 @@
|
||||
|
||||
<!-- Contacts View -->
|
||||
<div id="view-contacts" class="view view-contacts hidden">
|
||||
<div class="contacts-header">
|
||||
<h2>Kontakte</h2>
|
||||
<button id="btn-new-contact" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i>
|
||||
Neuer Kontakt
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="contacts-controls">
|
||||
<div class="contacts-filters">
|
||||
<select id="contacts-tag-filter">
|
||||
<option value="">Alle Tags</option>
|
||||
</select>
|
||||
<select id="contacts-sort">
|
||||
<option value="created_at-desc">Neueste zuerst</option>
|
||||
<option value="created_at-asc">Älteste zuerst</option>
|
||||
<option value="name-asc">Name (A-Z)</option>
|
||||
<option value="name-desc">Name (Z-A)</option>
|
||||
<option value="company-asc">Firma (A-Z)</option>
|
||||
<option value="company-desc">Firma (Z-A)</option>
|
||||
</select>
|
||||
<div class="view-wrapper">
|
||||
<div class="contacts-header">
|
||||
<h2>Kontakte</h2>
|
||||
<div class="header-actions">
|
||||
<div class="contacts-stats">
|
||||
<span id="contacts-count">0 Kontakte</span>
|
||||
</div>
|
||||
<button id="btn-export-contacts" class="btn btn-secondary btn-export">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Exportieren
|
||||
</button>
|
||||
<button id="btn-new-contact" class="btn btn-primary">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18">
|
||||
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Neuer Kontakt
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="contacts-grid" class="contacts-grid">
|
||||
<!-- Contact cards will be rendered here -->
|
||||
</div>
|
||||
<div class="contacts-controls">
|
||||
<div class="contacts-controls-top">
|
||||
<div class="bulk-actions hidden" id="bulk-actions">
|
||||
<span class="bulk-actions-info"><span id="selected-count">0</span> ausgewählt</span>
|
||||
<button id="btn-bulk-delete" class="btn btn-danger btn-sm">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v12a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14M10 11v6M14 11v6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Löschen
|
||||
</button>
|
||||
<button id="btn-deselect-all" class="btn btn-secondary btn-sm">Abwählen</button>
|
||||
</div>
|
||||
<div class="contacts-filters">
|
||||
<div class="filter-group">
|
||||
<label for="contacts-tag-filter" class="sr-only">Tags filtern</label>
|
||||
<select id="contacts-tag-filter" class="filter-select">
|
||||
<option value="">Alle Tags</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="contacts-empty" class="contacts-empty hidden">
|
||||
<i class="fas fa-address-book"></i>
|
||||
<h3>Keine Kontakte vorhanden</h3>
|
||||
<p>Erstellen Sie Ihren ersten Kontakt.</p>
|
||||
<button class="btn btn-primary" onclick="document.getElementById('btn-new-contact').click()">
|
||||
<i class="fas fa-plus"></i>
|
||||
Ersten Kontakt erstellen
|
||||
</button>
|
||||
<div class="contacts-table-container">
|
||||
<table class="contacts-table" id="contacts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="checkbox-cell">
|
||||
<input type="checkbox" class="table-checkbox" id="select-all-contacts">
|
||||
</th>
|
||||
<th class="sortable" data-sort="name">Name</th>
|
||||
<th class="sortable" data-sort="company">Firma</th>
|
||||
<th class="sortable hide-mobile" data-sort="position">Position</th>
|
||||
<th class="sortable" data-sort="email">E-Mail</th>
|
||||
<th class="hide-mobile">Telefon</th>
|
||||
<th>Tags</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contacts-tbody">
|
||||
<!-- Table rows will be rendered here -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination hidden" id="contacts-pagination">
|
||||
<button class="pagination-btn" id="btn-prev-page" disabled>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="pagination-info">
|
||||
Seite <span id="current-page">1</span> von <span id="total-pages">1</span>
|
||||
</span>
|
||||
<button class="pagination-btn" id="btn-next-page" disabled>
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="contacts-empty" class="contacts-empty hidden">
|
||||
<div class="empty-icon">
|
||||
<svg viewBox="0 0 24 24" width="64" height="64" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Keine Kontakte vorhanden</h3>
|
||||
<p>Erstellen Sie Ihren ersten Kontakt.</p>
|
||||
<button class="btn btn-primary" onclick="document.getElementById('btn-new-contact').click()">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18">
|
||||
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Ersten Kontakt erstellen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1915,23 +2030,19 @@
|
||||
<input type="hidden" id="reminder-color" value="#F59E0B">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reminder-advance">Erinnerung</label>
|
||||
<label for="reminder-advance">Vorab-Erinnerung</label>
|
||||
<div class="advance-options">
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox" name="advance-days" value="1" checked>
|
||||
<span class="checklist-checkbox"></span>
|
||||
<span class="checklist-text">1 Tag vorher</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox" name="advance-days" value="2">
|
||||
<span class="checklist-checkbox"></span>
|
||||
<span class="checklist-text">2 Tage vorher</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox" name="advance-days" value="3">
|
||||
<span class="checklist-checkbox"></span>
|
||||
<span class="checklist-text">3 Tage vorher</span>
|
||||
</label>
|
||||
<div class="reminder-advance-control">
|
||||
<div class="reminder-advance-inputs">
|
||||
<input type="number" id="reminder-advance-number" min="1" max="9" value="1" class="form-control reminder-number-input">
|
||||
<select id="reminder-advance-unit" class="form-control reminder-unit-select">
|
||||
<option value="day">Tag(e)</option>
|
||||
<option value="week">Woche(n)</option>
|
||||
<option value="month">Monat(e)</option>
|
||||
</select>
|
||||
<span class="reminder-advance-suffix">vorher</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1284,6 +1284,14 @@ class ApiClient {
|
||||
return this.post('/coding/validate-path', { path });
|
||||
}
|
||||
|
||||
async getCodingDirectoryUsage(id) {
|
||||
return this.get(`/coding/directories/${id}/usage`);
|
||||
}
|
||||
|
||||
async getCodingDirectoryUsageHistory(id, hours = 24) {
|
||||
return this.get(`/coding/directories/${id}/usage/history?hours=${hours}`);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTACTS
|
||||
// =============================================================================
|
||||
|
||||
@ -733,7 +733,7 @@ class App {
|
||||
users.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = user.username;
|
||||
option.textContent = user.displayName || user.email || 'Unbekannt';
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
@ -524,9 +524,19 @@ class CalendarViewManager {
|
||||
e.stopPropagation();
|
||||
this.createTaskForDate(dateString);
|
||||
}
|
||||
}, ['+ Aufgabe']);
|
||||
}, ['Aufgabe hinzufügen']);
|
||||
dayEl.appendChild(addBtn);
|
||||
|
||||
// Add reminder button
|
||||
const addReminderBtn = createElement('button', {
|
||||
className: 'calendar-week-add-reminder',
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
this.createReminderForDate(dateString);
|
||||
}
|
||||
}, ['Erinnerung hinzufügen']);
|
||||
dayEl.appendChild(addReminderBtn);
|
||||
|
||||
return dayEl;
|
||||
}
|
||||
|
||||
@ -920,28 +930,57 @@ class CalendarViewManager {
|
||||
className: 'btn btn-primary btn-block',
|
||||
style: { marginTop: 'var(--spacing-md)' },
|
||||
onclick: () => this.createTaskForDate(dateString)
|
||||
}, ['+ Aufgabe hinzufügen']));
|
||||
}, ['Aufgabe hinzufügen']));
|
||||
|
||||
// Add reminder button
|
||||
popup.appendChild(createElement('button', {
|
||||
className: 'btn btn-secondary btn-block',
|
||||
className: 'btn btn-reminder-secondary btn-block',
|
||||
style: { marginTop: 'var(--spacing-sm)' },
|
||||
onclick: () => this.createReminderForDate(dateString)
|
||||
}, ['🔔 Erinnerung hinzufügen']));
|
||||
}));
|
||||
|
||||
// Add HTML content with SVG icon
|
||||
const button = popup.lastElementChild;
|
||||
button.innerHTML = '<svg class="icon" viewBox="0 0 24 24" width="18" height="18" style="margin-right: 6px; flex-shrink: 0;"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0" stroke="white" stroke-width="2" fill="none"></path></svg> Erinnerung hinzufügen';
|
||||
|
||||
// Position popup - different logic for week vs month view
|
||||
const rect = anchorEl.getBoundingClientRect();
|
||||
const popupHeight = 400; // Estimated max height
|
||||
const popupWidth = 350;
|
||||
const padding = 8;
|
||||
|
||||
let popupTop, popupLeft;
|
||||
|
||||
if (this.viewMode === 'week') {
|
||||
// For week view, position at the top of the day element
|
||||
popupTop = Math.max(150, rect.top + 50); // Ensure it's visible, minimum 150px from top
|
||||
popupLeft = Math.min(rect.left, window.innerWidth - 350);
|
||||
} else {
|
||||
// For month view, position below the day element
|
||||
popupTop = rect.bottom + 8;
|
||||
popupLeft = Math.min(rect.left, window.innerWidth - 350);
|
||||
popupTop = rect.bottom + padding;
|
||||
|
||||
// Check if popup would go below viewport
|
||||
if (popupTop + popupHeight > window.innerHeight - padding) {
|
||||
// Position above the element instead
|
||||
popupTop = rect.top - popupHeight - padding;
|
||||
|
||||
// If still not enough space above, position at bottom of viewport
|
||||
if (popupTop < padding) {
|
||||
popupTop = window.innerHeight - popupHeight - padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal positioning
|
||||
popupLeft = rect.left;
|
||||
|
||||
// Check if popup would go beyond right edge
|
||||
if (popupLeft + popupWidth > window.innerWidth - padding) {
|
||||
popupLeft = window.innerWidth - popupWidth - padding;
|
||||
}
|
||||
|
||||
// Check if popup would go beyond left edge
|
||||
if (popupLeft < padding) {
|
||||
popupLeft = padding;
|
||||
}
|
||||
|
||||
popup.style.top = `${popupTop}px`;
|
||||
@ -1087,15 +1126,39 @@ class CalendarViewManager {
|
||||
|
||||
// Add reminder button
|
||||
popup.appendChild(createElement('button', {
|
||||
className: 'btn btn-secondary btn-block',
|
||||
className: 'btn btn-reminder-secondary btn-block',
|
||||
style: { marginTop: 'var(--spacing-md)' },
|
||||
onclick: () => this.createReminderForDate(dateString)
|
||||
}, ['+ Weitere Erinnerung']));
|
||||
}, ['Weitere Erinnerung']));
|
||||
|
||||
// Position popup
|
||||
const rect = anchorEl.getBoundingClientRect();
|
||||
let popupTop = rect.bottom + 8;
|
||||
let popupLeft = Math.min(rect.left, window.innerWidth - 350);
|
||||
const popupHeight = 400; // Estimated max height
|
||||
const popupWidth = 350;
|
||||
const padding = 8;
|
||||
|
||||
let popupTop = rect.bottom + padding;
|
||||
let popupLeft = rect.left;
|
||||
|
||||
// Check if popup would go below viewport
|
||||
if (popupTop + popupHeight > window.innerHeight - padding) {
|
||||
// Position above the element instead
|
||||
popupTop = rect.top - popupHeight - padding;
|
||||
|
||||
// If still not enough space above, position at bottom of viewport
|
||||
if (popupTop < padding) {
|
||||
popupTop = window.innerHeight - popupHeight - padding;
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal positioning
|
||||
if (popupLeft + popupWidth > window.innerWidth - padding) {
|
||||
popupLeft = window.innerWidth - popupWidth - padding;
|
||||
}
|
||||
|
||||
if (popupLeft < padding) {
|
||||
popupLeft = padding;
|
||||
}
|
||||
|
||||
popup.style.top = `${popupTop}px`;
|
||||
popup.style.left = `${popupLeft}px`;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* TASKMATE - Contacts Manager
|
||||
* ===========================
|
||||
* Kontaktverwaltung mit Kartenansicht
|
||||
* Kontaktverwaltung mit Tabellenansicht
|
||||
*/
|
||||
|
||||
import api from './api.js';
|
||||
@ -12,11 +12,14 @@ class ContactsManager {
|
||||
constructor() {
|
||||
this.contacts = [];
|
||||
this.filteredContacts = [];
|
||||
this.selectedContacts = new Set();
|
||||
this.allTags = new Set();
|
||||
this.searchQuery = '';
|
||||
this.filterTag = '';
|
||||
this.sortBy = 'created_at';
|
||||
this.sortOrder = 'desc';
|
||||
this.currentPage = 1;
|
||||
this.itemsPerPage = 25;
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
@ -30,16 +33,30 @@ class ContactsManager {
|
||||
|
||||
// DOM Elements
|
||||
this.contactsView = $('#view-contacts');
|
||||
this.contactsGrid = $('#contacts-grid');
|
||||
this.contactsTable = $('#contacts-table');
|
||||
this.contactsTbody = $('#contacts-tbody');
|
||||
this.contactsEmpty = $('#contacts-empty');
|
||||
this.tagFilter = $('#contacts-tag-filter');
|
||||
this.sortSelect = $('#contacts-sort');
|
||||
this.selectAllCheckbox = $('#select-all-contacts');
|
||||
this.bulkActions = $('#bulk-actions');
|
||||
this.selectedCountSpan = $('#selected-count');
|
||||
this.contactsCountSpan = $('#contacts-count');
|
||||
this.newContactBtn = $('#btn-new-contact');
|
||||
this.exportBtn = $('#btn-export-contacts');
|
||||
this.bulkDeleteBtn = $('#btn-bulk-delete');
|
||||
this.deselectAllBtn = $('#btn-deselect-all');
|
||||
|
||||
// Pagination
|
||||
this.pagination = $('#contacts-pagination');
|
||||
this.currentPageSpan = $('#current-page');
|
||||
this.totalPagesSpan = $('#total-pages');
|
||||
this.prevPageBtn = $('#btn-prev-page');
|
||||
this.nextPageBtn = $('#btn-next-page');
|
||||
|
||||
console.log('[Contacts] DOM Elements check:');
|
||||
console.log(' contactsView:', this.contactsView);
|
||||
console.log(' newContactBtn:', this.newContactBtn);
|
||||
console.log(' contactsGrid:', this.contactsGrid);
|
||||
console.log(' contactsTable:', this.contactsTable);
|
||||
|
||||
// Modal Elements
|
||||
this.contactModal = $('#contact-modal');
|
||||
@ -87,19 +104,46 @@ class ContactsManager {
|
||||
if (this.tagFilter) {
|
||||
this.tagFilter.addEventListener('change', (e) => {
|
||||
this.filterTag = e.target.value;
|
||||
this.currentPage = 1;
|
||||
this.filterContacts();
|
||||
});
|
||||
}
|
||||
|
||||
// Sort
|
||||
if (this.sortSelect) {
|
||||
this.sortSelect.addEventListener('change', (e) => {
|
||||
const [sortBy, sortOrder] = e.target.value.split('-');
|
||||
this.sortBy = sortBy;
|
||||
this.sortOrder = sortOrder;
|
||||
// Table sorting
|
||||
$$('.sortable').forEach(th => {
|
||||
th.addEventListener('click', (e) => {
|
||||
const sortField = th.dataset.sort;
|
||||
if (this.sortBy === sortField) {
|
||||
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.sortBy = sortField;
|
||||
this.sortOrder = 'asc';
|
||||
}
|
||||
|
||||
// Update UI
|
||||
$$('.sortable').forEach(el => el.classList.remove('sort-asc', 'sort-desc'));
|
||||
th.classList.add(this.sortOrder === 'asc' ? 'sort-asc' : 'sort-desc');
|
||||
|
||||
this.sortContacts();
|
||||
this.renderContacts();
|
||||
});
|
||||
});
|
||||
|
||||
// Select all checkbox
|
||||
if (this.selectAllCheckbox) {
|
||||
this.selectAllCheckbox.addEventListener('change', (e) => {
|
||||
const checked = e.target.checked;
|
||||
const visibleContacts = this.getPaginatedContacts();
|
||||
|
||||
if (checked) {
|
||||
visibleContacts.forEach(contact => this.selectedContacts.add(contact.id));
|
||||
} else {
|
||||
visibleContacts.forEach(contact => this.selectedContacts.delete(contact.id));
|
||||
}
|
||||
|
||||
this.updateBulkActions();
|
||||
this.renderContacts();
|
||||
});
|
||||
}
|
||||
|
||||
// New Contact Button
|
||||
@ -114,6 +158,46 @@ class ContactsManager {
|
||||
console.warn('[Contacts] newContactBtn not found!');
|
||||
}
|
||||
|
||||
// Export Button
|
||||
if (this.exportBtn) {
|
||||
this.exportBtn.addEventListener('click', () => this.exportContacts());
|
||||
}
|
||||
|
||||
// Bulk Delete Button
|
||||
if (this.bulkDeleteBtn) {
|
||||
this.bulkDeleteBtn.addEventListener('click', () => this.bulkDelete());
|
||||
}
|
||||
|
||||
// Deselect All Button
|
||||
if (this.deselectAllBtn) {
|
||||
this.deselectAllBtn.addEventListener('click', () => {
|
||||
this.selectedContacts.clear();
|
||||
this.selectAllCheckbox.checked = false;
|
||||
this.updateBulkActions();
|
||||
this.renderContacts();
|
||||
});
|
||||
}
|
||||
|
||||
// Pagination buttons
|
||||
if (this.prevPageBtn) {
|
||||
this.prevPageBtn.addEventListener('click', () => {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--;
|
||||
this.renderContacts();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.nextPageBtn) {
|
||||
this.nextPageBtn.addEventListener('click', () => {
|
||||
const totalPages = Math.ceil(this.filteredContacts.length / this.itemsPerPage);
|
||||
if (this.currentPage < totalPages) {
|
||||
this.currentPage++;
|
||||
this.renderContacts();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Modal Form
|
||||
if (this.contactForm) {
|
||||
this.contactForm.addEventListener('submit', (e) => {
|
||||
@ -161,6 +245,7 @@ class ContactsManager {
|
||||
socket.on('contact:deleted', (data) => {
|
||||
console.log('[Contacts] Socket: contact deleted', data);
|
||||
this.contacts = this.contacts.filter(c => c.id !== data.contactId);
|
||||
this.selectedContacts.delete(data.contactId);
|
||||
this.updateTagsList();
|
||||
this.filterContacts();
|
||||
});
|
||||
@ -216,6 +301,7 @@ class ContactsManager {
|
||||
});
|
||||
|
||||
this.sortContacts();
|
||||
this.updateContactsCount();
|
||||
this.renderContacts();
|
||||
}
|
||||
|
||||
@ -243,56 +329,77 @@ class ContactsManager {
|
||||
});
|
||||
}
|
||||
|
||||
getPaginatedContacts() {
|
||||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const end = start + this.itemsPerPage;
|
||||
return this.filteredContacts.slice(start, end);
|
||||
}
|
||||
|
||||
renderContacts() {
|
||||
if (!this.contactsGrid) return;
|
||||
if (!this.contactsTbody) return;
|
||||
|
||||
if (this.filteredContacts.length === 0) {
|
||||
this.contactsGrid.classList.add('hidden');
|
||||
this.contactsTable.parentElement.classList.add('hidden');
|
||||
this.contactsEmpty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
this.contactsGrid.classList.remove('hidden');
|
||||
this.contactsTable.parentElement.classList.remove('hidden');
|
||||
this.contactsEmpty.classList.add('hidden');
|
||||
|
||||
const html = this.filteredContacts.map(contact => this.createContactCard(contact)).join('');
|
||||
this.contactsGrid.innerHTML = html;
|
||||
const paginatedContacts = this.getPaginatedContacts();
|
||||
const html = paginatedContacts.map(contact => this.createContactRow(contact)).join('');
|
||||
this.contactsTbody.innerHTML = html;
|
||||
|
||||
// Bind card events
|
||||
this.bindCardEvents();
|
||||
// Update pagination
|
||||
this.updatePagination();
|
||||
|
||||
// Bind row events
|
||||
this.bindRowEvents();
|
||||
}
|
||||
|
||||
createContactCard(contact) {
|
||||
createContactRow(contact) {
|
||||
const displayName = this.getContactDisplayName(contact);
|
||||
const initials = this.getContactInitials(contact);
|
||||
const tags = contact.tags || [];
|
||||
const isSelected = this.selectedContacts.has(contact.id);
|
||||
|
||||
return `
|
||||
<div class="contact-card" data-contact-id="${contact.id}">
|
||||
<div class="contact-card-header">
|
||||
<div class="contact-avatar">
|
||||
${initials}
|
||||
<tr data-contact-id="${contact.id}" ${isSelected ? 'class="selected"' : ''}>
|
||||
<td class="checkbox-cell">
|
||||
<input type="checkbox" class="table-checkbox contact-checkbox" data-contact-id="${contact.id}" ${isSelected ? 'checked' : ''}>
|
||||
</td>
|
||||
<td>
|
||||
<div class="name-cell">
|
||||
<div class="contact-avatar-small">${initials}</div>
|
||||
<a href="#" class="contact-name-link" data-contact-id="${contact.id}">${displayName}</a>
|
||||
</div>
|
||||
<div class="contact-actions">
|
||||
<button class="btn-icon btn-edit-contact" title="Bearbeiten">
|
||||
<i class="fas fa-edit"></i>
|
||||
</td>
|
||||
<td>${contact.company || '-'}</td>
|
||||
<td class="hide-mobile">${contact.position || '-'}</td>
|
||||
<td>${contact.email ? `<a href="mailto:${contact.email}">${contact.email}</a>` : '-'}</td>
|
||||
<td class="hide-mobile">${contact.phone || '-'}</td>
|
||||
<td>
|
||||
<div class="tags-cell">
|
||||
${tags.length > 0 ? tags.map(tag => `<span class="contact-tag">${tag}</span>`).join('') : '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<div class="table-actions">
|
||||
<button class="btn-table-action btn-edit-contact" data-contact-id="${contact.id}" title="Bearbeiten">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-table-action btn-delete-contact-inline" data-contact-id="${contact.id}" title="Löschen">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v12a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14M10 11v6M14 11v6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-card-body">
|
||||
<h3 class="contact-name">${displayName}</h3>
|
||||
${contact.company ? `<div class="contact-company">${contact.company}</div>` : ''}
|
||||
${contact.position ? `<div class="contact-position">${contact.position}</div>` : ''}
|
||||
${contact.email ? `<div class="contact-email"><i class="fas fa-envelope"></i> ${contact.email}</div>` : ''}
|
||||
${contact.phone ? `<div class="contact-phone"><i class="fas fa-phone"></i> ${contact.phone}</div>` : ''}
|
||||
${contact.mobile ? `<div class="contact-mobile"><i class="fas fa-mobile"></i> ${contact.mobile}</div>` : ''}
|
||||
</div>
|
||||
${tags.length > 0 ? `
|
||||
<div class="contact-tags">
|
||||
${tags.map(tag => `<span class="contact-tag">${tag}</span>`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -325,26 +432,97 @@ class ContactsManager {
|
||||
return initials || '?';
|
||||
}
|
||||
|
||||
bindCardEvents() {
|
||||
bindRowEvents() {
|
||||
// Contact checkboxes
|
||||
$$('.contact-checkbox').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
const contactId = parseInt(e.target.dataset.contactId);
|
||||
if (e.target.checked) {
|
||||
this.selectedContacts.add(contactId);
|
||||
} else {
|
||||
this.selectedContacts.delete(contactId);
|
||||
}
|
||||
this.updateBulkActions();
|
||||
this.updateRowSelection(contactId, e.target.checked);
|
||||
});
|
||||
});
|
||||
|
||||
// Edit buttons
|
||||
$$('.btn-edit-contact').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const card = btn.closest('.contact-card');
|
||||
const contactId = parseInt(card.dataset.contactId);
|
||||
const contactId = parseInt(btn.dataset.contactId);
|
||||
this.editContact(contactId);
|
||||
});
|
||||
});
|
||||
|
||||
// Card click
|
||||
$$('.contact-card').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const contactId = parseInt(card.dataset.contactId);
|
||||
// Delete inline buttons
|
||||
$$('.btn-delete-contact-inline').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const contactId = parseInt(btn.dataset.contactId);
|
||||
this.deleteContactInline(contactId);
|
||||
});
|
||||
});
|
||||
|
||||
// Name links
|
||||
$$('.contact-name-link').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const contactId = parseInt(link.dataset.contactId);
|
||||
this.editContact(contactId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateRowSelection(contactId, selected) {
|
||||
const row = $(`tr[data-contact-id="${contactId}"]`);
|
||||
if (row) {
|
||||
if (selected) {
|
||||
row.classList.add('selected');
|
||||
} else {
|
||||
row.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBulkActions() {
|
||||
const count = this.selectedContacts.size;
|
||||
if (count > 0) {
|
||||
this.bulkActions.classList.remove('hidden');
|
||||
this.selectedCountSpan.textContent = count;
|
||||
} else {
|
||||
this.bulkActions.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Update select all checkbox state
|
||||
const visibleContacts = this.getPaginatedContacts();
|
||||
const allSelected = visibleContacts.length > 0 &&
|
||||
visibleContacts.every(contact => this.selectedContacts.has(contact.id));
|
||||
this.selectAllCheckbox.checked = allSelected;
|
||||
}
|
||||
|
||||
updateContactsCount() {
|
||||
const count = this.filteredContacts.length;
|
||||
this.contactsCountSpan.textContent = `${count} ${count === 1 ? 'Kontakt' : 'Kontakte'}`;
|
||||
}
|
||||
|
||||
updatePagination() {
|
||||
const totalPages = Math.ceil(this.filteredContacts.length / this.itemsPerPage);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
this.pagination.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
this.pagination.classList.remove('hidden');
|
||||
this.currentPageSpan.textContent = this.currentPage;
|
||||
this.totalPagesSpan.textContent = totalPages;
|
||||
|
||||
this.prevPageBtn.disabled = this.currentPage === 1;
|
||||
this.nextPageBtn.disabled = this.currentPage === totalPages;
|
||||
}
|
||||
|
||||
updateTagsList() {
|
||||
// Collect all unique tags
|
||||
this.allTags.clear();
|
||||
@ -370,6 +548,120 @@ class ContactsManager {
|
||||
}
|
||||
}
|
||||
|
||||
async bulkDelete() {
|
||||
const count = this.selectedContacts.size;
|
||||
if (count === 0) return;
|
||||
|
||||
const contactNames = Array.from(this.selectedContacts).map(id => {
|
||||
const contact = this.contacts.find(c => c.id === id);
|
||||
return contact ? this.getContactDisplayName(contact) : '';
|
||||
}).filter(Boolean);
|
||||
|
||||
const message = count === 1
|
||||
? `Möchten Sie den Kontakt "${contactNames[0]}" wirklich löschen?`
|
||||
: `Möchten Sie ${count} Kontakte wirklich löschen?`;
|
||||
|
||||
if (!confirm(message)) return;
|
||||
|
||||
try {
|
||||
// Delete contacts one by one
|
||||
for (const contactId of this.selectedContacts) {
|
||||
await api.deleteContact(contactId);
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: `${count} ${count === 1 ? 'Kontakt' : 'Kontakte'} gelöscht`, type: 'success' }
|
||||
}));
|
||||
|
||||
this.selectedContacts.clear();
|
||||
await this.loadContacts();
|
||||
} catch (error) {
|
||||
console.error('[Contacts] Error during bulk delete:', error);
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: 'Fehler beim Löschen der Kontakte', type: 'error' }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async deleteContactInline(contactId) {
|
||||
const contact = this.contacts.find(c => c.id === contactId);
|
||||
if (!contact) return;
|
||||
|
||||
const displayName = this.getContactDisplayName(contact);
|
||||
|
||||
if (!confirm(`Möchten Sie den Kontakt "${displayName}" wirklich löschen?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.deleteContact(contactId);
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: 'Kontakt gelöscht', type: 'success' }
|
||||
}));
|
||||
await this.loadContacts();
|
||||
} catch (error) {
|
||||
console.error('[Contacts] Error deleting contact:', error);
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: 'Fehler beim Löschen', type: 'error' }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async exportContacts() {
|
||||
try {
|
||||
let contactsToExport = this.filteredContacts;
|
||||
|
||||
// Create CSV content
|
||||
const headers = ['Vorname', 'Nachname', 'Firma', 'Position', 'E-Mail', 'Telefon', 'Mobil', 'Adresse', 'PLZ', 'Stadt', 'Land', 'Website', 'Tags', 'Notizen'];
|
||||
const rows = [headers];
|
||||
|
||||
contactsToExport.forEach(contact => {
|
||||
const row = [
|
||||
contact.firstName || '',
|
||||
contact.lastName || '',
|
||||
contact.company || '',
|
||||
contact.position || '',
|
||||
contact.email || '',
|
||||
contact.phone || '',
|
||||
contact.mobile || '',
|
||||
contact.address || '',
|
||||
contact.postalCode || '',
|
||||
contact.city || '',
|
||||
contact.country || '',
|
||||
contact.website || '',
|
||||
(contact.tags || []).join(', '),
|
||||
contact.notes || ''
|
||||
];
|
||||
rows.push(row);
|
||||
});
|
||||
|
||||
// Convert to CSV
|
||||
const csvContent = rows.map(row =>
|
||||
row.map(cell => `"${cell.replace(/"/g, '""')}"`).join(',')
|
||||
).join('\n');
|
||||
|
||||
// Create blob and download
|
||||
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `kontakte_${new Date().toISOString().split('T')[0]}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: `${contactsToExport.length} Kontakte exportiert`, type: 'success' }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('[Contacts] Error exporting contacts:', error);
|
||||
window.dispatchEvent(new CustomEvent('toast:show', {
|
||||
detail: { message: 'Fehler beim Exportieren', type: 'error' }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
showContactModal(contact = null) {
|
||||
console.log('[Contacts] showContactModal called with:', contact);
|
||||
console.log('[Contacts] contactModal element:', this.contactModal);
|
||||
|
||||
@ -18,6 +18,7 @@ class KnowledgeManager {
|
||||
this.expandedEntries = new Set();
|
||||
this.initialized = false;
|
||||
this.searchDebounceTimer = null;
|
||||
this.pendingFiles = null;
|
||||
|
||||
// Drag & Drop State
|
||||
this.draggedCategoryId = null;
|
||||
@ -140,6 +141,14 @@ class KnowledgeManager {
|
||||
|
||||
// File Upload
|
||||
this.fileInput?.addEventListener('change', (e) => this.handleFileSelect(e));
|
||||
|
||||
// Click on file upload area to trigger file input
|
||||
this.fileUploadArea?.addEventListener('click', (e) => {
|
||||
// Don't trigger if clicking on the label (it has its own handler)
|
||||
if (!e.target.closest('.file-input-label')) {
|
||||
this.fileInput?.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag & Drop for file upload
|
||||
if (this.fileUploadArea) {
|
||||
@ -382,8 +391,8 @@ class KnowledgeManager {
|
||||
<div class="knowledge-attachment-item" data-attachment-id="${att.id}">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2" fill="none"/><path d="M14 2v6h6" stroke="currentColor" stroke-width="2" fill="none"/></svg>
|
||||
<div class="knowledge-attachment-info">
|
||||
<span class="knowledge-attachment-name">${this.escapeHtml(att.original_name)}</span>
|
||||
<span class="knowledge-attachment-size">${this.formatFileSize(att.size_bytes)}</span>
|
||||
<span class="knowledge-attachment-name">${this.escapeHtml(att.originalName || att.original_name || '')}</span>
|
||||
<span class="knowledge-attachment-size">${this.formatFileSize(att.sizeBytes || att.size_bytes || 0)}</span>
|
||||
</div>
|
||||
<div class="knowledge-attachment-actions">
|
||||
<a href="${api.getKnowledgeAttachmentDownloadUrl(att.id)}" class="btn-icon" title="Herunterladen" download>
|
||||
@ -957,7 +966,8 @@ class KnowledgeManager {
|
||||
this.entryModalTitle.textContent = isEdit ? 'Eintrag bearbeiten' : 'Neuer Eintrag';
|
||||
this.entryForm?.reset();
|
||||
this.deleteEntryBtn?.classList.toggle('hidden', !isEdit);
|
||||
this.attachmentsSection.style.display = isEdit ? 'block' : 'none';
|
||||
// Show attachments section always, but with a note for new entries
|
||||
this.attachmentsSection.style.display = 'block';
|
||||
|
||||
if (isEdit) {
|
||||
const entry = await this.loadEntryWithAttachments(entryId);
|
||||
@ -972,7 +982,7 @@ class KnowledgeManager {
|
||||
} else {
|
||||
this.entryIdInput.value = '';
|
||||
this.entryCategoryIdInput.value = this.selectedCategory?.id || '';
|
||||
this.attachmentsContainer.innerHTML = '';
|
||||
this.attachmentsContainer.innerHTML = '<p class="text-muted" style="text-align: center; padding: 20px; color: var(--text-secondary); font-size: var(--text-sm);">Speichern Sie zuerst den Eintrag, um Dateien hochzuladen.</p>';
|
||||
}
|
||||
|
||||
this.openModal(this.entryModal, 'knowledge-entry-modal');
|
||||
@ -1010,8 +1020,15 @@ class KnowledgeManager {
|
||||
await api.updateKnowledgeEntry(entryId, data);
|
||||
this.showToast('Eintrag aktualisiert', 'success');
|
||||
} else {
|
||||
await api.createKnowledgeEntry(data);
|
||||
const newEntry = await api.createKnowledgeEntry(data);
|
||||
this.showToast('Eintrag erstellt', 'success');
|
||||
|
||||
// If there are pending files, upload them now
|
||||
if (this.pendingFiles && this.pendingFiles.length > 0) {
|
||||
this.showToast('Lade Dateien hoch...', 'info');
|
||||
await this.uploadFiles(this.pendingFiles);
|
||||
this.pendingFiles = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.closeEntryModal();
|
||||
@ -1071,7 +1088,10 @@ class KnowledgeManager {
|
||||
async uploadFiles(files) {
|
||||
const entryId = parseInt(this.entryIdInput?.value);
|
||||
if (!entryId) {
|
||||
this.showToast('Bitte zuerst den Eintrag speichern', 'error');
|
||||
// For new entries, show a more helpful message
|
||||
this.showToast('Speichern Sie zuerst den Eintrag, dann können Sie Dateien hochladen', 'info');
|
||||
// Store files temporarily to upload after save
|
||||
this.pendingFiles = files;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -237,9 +237,10 @@ class ReminderManager {
|
||||
$('#reminder-color').value = '#F59E0B';
|
||||
|
||||
// Reset advance days
|
||||
$$('input[name="advance-days"]').forEach(cb => {
|
||||
cb.checked = cb.value === '1';
|
||||
});
|
||||
const advanceNumberEl = $('#reminder-advance-number');
|
||||
const advanceUnitEl = $('#reminder-advance-unit');
|
||||
if (advanceNumberEl) advanceNumberEl.value = '1';
|
||||
if (advanceUnitEl) advanceUnitEl.value = 'day';
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,11 +343,20 @@ class ReminderManager {
|
||||
const freshOptions = $('#reminder-assignee-options');
|
||||
const freshValueDisplay = freshTrigger.querySelector('.custom-select-value');
|
||||
|
||||
// Clear any inline styles from dropdown
|
||||
freshOptions.style.width = '';
|
||||
freshOptions.style.left = '';
|
||||
freshOptions.style.top = '';
|
||||
freshOptions.style.bottom = '';
|
||||
|
||||
// Toggle dropdown
|
||||
freshTrigger.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
wrapper.classList.toggle('open');
|
||||
|
||||
// Simple toggle - CSS handles positioning
|
||||
|
||||
console.log('[Reminder] Dropdown toggled, open:', wrapper.classList.contains('open'));
|
||||
});
|
||||
|
||||
@ -410,9 +420,23 @@ class ReminderManager {
|
||||
|
||||
// Set advance days
|
||||
const advanceDays = reminder.advance_days || ['1'];
|
||||
$$('input[name="advance-days"]').forEach(cb => {
|
||||
cb.checked = advanceDays.includes(cb.value);
|
||||
});
|
||||
const advanceNumberEl = $('#reminder-advance-number');
|
||||
const advanceUnitEl = $('#reminder-advance-unit');
|
||||
|
||||
// Parse the first advance day value to set number and unit
|
||||
if (advanceDays.length > 0 && advanceNumberEl && advanceUnitEl) {
|
||||
const advanceValue = parseInt(advanceDays[0]);
|
||||
if (advanceValue <= 9) {
|
||||
advanceNumberEl.value = advanceValue;
|
||||
advanceUnitEl.value = 'day';
|
||||
} else if (advanceValue % 7 === 0 && advanceValue <= 63) {
|
||||
advanceNumberEl.value = advanceValue / 7;
|
||||
advanceUnitEl.value = 'week';
|
||||
} else if (advanceValue % 30 === 0 && advanceValue <= 270) {
|
||||
advanceNumberEl.value = advanceValue / 30;
|
||||
advanceUnitEl.value = 'month';
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading reminder:', error);
|
||||
@ -434,16 +458,19 @@ class ReminderManager {
|
||||
try {
|
||||
const formData = new FormData(this.form);
|
||||
|
||||
// Get advance days
|
||||
const advanceDays = [];
|
||||
$$('input[name="advance-days"]:checked').forEach(cb => {
|
||||
advanceDays.push(cb.value);
|
||||
});
|
||||
// Get advance days from new inputs
|
||||
const advanceNumber = parseInt($('#reminder-advance-number').value) || 1;
|
||||
const advanceUnit = $('#reminder-advance-unit').value || 'day';
|
||||
|
||||
if (advanceDays.length === 0) {
|
||||
throw new Error('Bitte wählen Sie mindestens eine Erinnerungszeit aus');
|
||||
let advanceDaysValue = advanceNumber;
|
||||
if (advanceUnit === 'week') {
|
||||
advanceDaysValue = advanceNumber * 7;
|
||||
} else if (advanceUnit === 'month') {
|
||||
advanceDaysValue = advanceNumber * 30;
|
||||
}
|
||||
|
||||
const advanceDays = [String(advanceDaysValue)];
|
||||
|
||||
const data = {
|
||||
project_id: store.get('currentProjectId'),
|
||||
title: formData.get('reminder-title') || $('#reminder-title').value,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* Offline support and caching
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = '265';
|
||||
const CACHE_VERSION = '292';
|
||||
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
|
||||
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
|
||||
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren