feat: rebuild admin account management and ticket assignment

This commit is contained in:
Webklar Deploy committed 2026-07-11 15:19:54 +00:00
1 parent 27b365622e
commit f76b738f5f
11 files changed
+1239 -1035

No files matched your search

+60 -62
View File
@@ -1,80 +1,78 @@
import { useState } from 'react'
import { FaEye, FaEyeSlash, FaCheck } from 'react-icons/fa6'
import { useEffect, useRef, useState } from 'react'
import { FaCheck, FaCopy, FaEye, FaEyeSlash } from 'react-icons/fa6'
/**
* Anzeige mit Auge: Klick kopiert den Wert und schaltet Sichtbarkeit um.
*/
export default function CopyableCredential({ value }) {
const [visible, setVisible] = useState(false)
async function copyText(text) {
try {
await navigator.clipboard.writeText(text)
} catch {
const textarea = document.createElement('textarea')
textarea.value = text
textarea.setAttribute('readonly', '')
textarea.style.position = 'fixed'
textarea.style.left = '-9999px'
document.body.appendChild(textarea)
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
}
}
export default function CopyableCredential({ value, secret = false, label = 'Wert' }) {
const [visible, setVisible] = useState(!secret)
const [copied, setCopied] = useState(false)
const text = (value || '').trim()
const timeoutRef = useRef(null)
const text = String(value || '').trim()
if (!text) {
return <span style={{ color: '#9ca3af' }}>–</span>
}
useEffect(() => {
setVisible(!secret)
}, [secret, text])
const handleClick = async (e) => {
e.preventDefault()
e.stopPropagation()
setVisible((v) => !v)
try {
await navigator.clipboard.writeText(text)
} catch {
const ta = document.createElement('textarea')
ta.value = text
ta.setAttribute('readonly', '')
ta.style.position = 'fixed'
ta.style.left = '-9999px'
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
}
useEffect(() => () => clearTimeout(timeoutRef.current), [])
if (!text) return <span className="credential-empty">–</span>
const handleCopy = async (event) => {
event.preventDefault()
event.stopPropagation()
await copyText(text)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => setCopied(false), 1800)
}
const masked = '•'.repeat(Math.min(Math.max(text.length, 8), 16))
const display = visible ? text : masked
const displayValue = secret && !visible ? '••••••••••••' : text
return (
<span
className="copyable-credential"
style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', maxWidth: '100%' }}
>
<span className="credential">
<span
style={{
fontFamily: visible ? 'inherit' : 'monospace',
letterSpacing: visible ? 'normal' : '1px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '180px',
}}
title={visible ? text : 'Klick auf das Auge zum Anzeigen und Kopieren'}
className={`credential-value ${secret && !visible ? 'is-masked' : ''}`}
title={secret && !visible ? `${label} anzeigen` : text}
>
{display}
{displayValue}
</span>
{secret && (
<button
type="button"
className="credential-action"
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setVisible((current) => !current)
}}
title={visible ? `${label} ausblenden` : `${label} anzeigen`}
aria-label={visible ? `${label} ausblenden` : `${label} anzeigen`}
>
{visible ? <FaEyeSlash /> : <FaEye />}
</button>
)}
<button
type="button"
className="btn"
onClick={handleClick}
title={copied ? 'Kopiert!' : 'Anzeigen und in Zwischenablage kopieren'}
style={{
padding: '2px 6px',
minWidth: '28px',
lineHeight: 1,
flexShrink: 0,
}}
aria-label={copied ? 'Kopiert' : 'Anzeigen und kopieren'}
className="credential-action"
onClick={handleCopy}
title={copied ? 'Kopiert' : `${label} kopieren`}
aria-label={copied ? `${label} kopiert` : `${label} kopieren`}
>
{copied ? (
<FaCheck style={{ color: '#059669' }} />
) : visible ? (
<FaEyeSlash />
) : (
<FaEye />
)}
{copied ? <FaCheck className="credential-check" /> : <FaCopy />}
</button>
</span>
)
+7 -26
View File
@@ -55,8 +55,6 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
topic: '',
requestedBy: '',
requestedFor: '',
assignedTo: '', // Zugewiesener Mitarbeiter (User ID)
status: 'Open', // Status wird automatisch gesetzt basierend auf assignedTo
startDate: today,
startTime: '',
deadline: today,
@@ -235,30 +233,13 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
</select>
</div>
<div className="form-group">
<label className="form-label">Assigned To</label>
<select
className="form-control"
value={formData.assignedTo}
onChange={(e) => {
const userId = e.target.value
handleChange('assignedTo', userId)
// Status-Automatik: Wenn Mitarbeiter zugewiesen → Status = "Assigned"
// Wenn kein Mitarbeiter → Status = "Open"
if (userId) {
handleChange('status', 'Assigned')
} else {
handleChange('status', 'Open')
}
}}
>
<option value="">Unassigned</option>
{employees.map(emp => (
<option key={emp.$id} value={emp.userId}>
{emp.displayName}{emp.shortcode ? ` (${emp.shortcode})` : ''}
</option>
))}
</select>
<div className="form-group automatic-assignment-note">
<label className="form-label">Zuweisung</label>
<p>
{employees.length > 0
? `Automatisch an den Mitarbeiter mit der geringsten Anzahl offener Tickets (${employees.length} verfügbar).`
: 'Es ist noch kein Mitarbeiter verfügbar. Das Ticket bleibt zunächst offen.'}
</p>
</div>
</div>