feat: rebuild admin account management and ticket assignment
This commit is contained in:
1 parent
27b365622e
commit
f76b738f5f
11 files changed
+1239
-1035
No files matched your search
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user