356 lines
9.7 KiB
TypeScript
356 lines
9.7 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import {
|
|
PrimaryButton,
|
|
DefaultButton,
|
|
TextField,
|
|
Dropdown,
|
|
IDropdownOption,
|
|
Stack,
|
|
Text,
|
|
Toggle,
|
|
Spinner,
|
|
SpinnerSize,
|
|
MessageBar,
|
|
MessageBarType,
|
|
} from "@fluentui/react";
|
|
import {
|
|
SyncProfile,
|
|
StarfaceConnection,
|
|
StarfaceAddressBook,
|
|
OutlookContactFolder,
|
|
} from "../../models/types";
|
|
import { ProfileManager } from "../../services/profile-manager";
|
|
import { SyncEngine } from "../../services/sync-engine";
|
|
|
|
interface Props {
|
|
profile: SyncProfile | null;
|
|
onSave: () => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
const defaultConnection: StarfaceConnection = {
|
|
host: "",
|
|
port: 443,
|
|
useSsl: true,
|
|
loginId: "",
|
|
password: "",
|
|
};
|
|
|
|
export const ProfileEditor: React.FC<Props> = ({
|
|
profile,
|
|
onSave,
|
|
onCancel,
|
|
}) => {
|
|
const pm = new ProfileManager();
|
|
const engine = new SyncEngine();
|
|
const isNew = !profile;
|
|
|
|
const [name, setName] = useState(profile?.name || "");
|
|
const [connection, setConnection] = useState<StarfaceConnection>(
|
|
profile?.starfaceConnection || { ...defaultConnection }
|
|
);
|
|
const [addressBooks, setAddressBooks] = useState<StarfaceAddressBook[]>([]);
|
|
const [selectedBook, setSelectedBook] = useState<StarfaceAddressBook | null>(
|
|
profile?.starfaceAddressBook || null
|
|
);
|
|
const [outlookFolders, setOutlookFolders] = useState<OutlookContactFolder[]>(
|
|
[]
|
|
);
|
|
const [selectedFolderId, setSelectedFolderId] = useState(
|
|
profile?.outlookFolderId || ""
|
|
);
|
|
const [syncDirection, setSyncDirection] = useState<SyncProfile["syncDirection"]>(
|
|
profile?.syncDirection || "both"
|
|
);
|
|
const [enabled, setEnabled] = useState(profile?.enabled ?? true);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [testResult, setTestResult] = useState<{
|
|
success: boolean;
|
|
message: string;
|
|
} | null>(null);
|
|
const [error, setError] = useState("");
|
|
|
|
// Load Outlook folders on mount
|
|
useEffect(() => {
|
|
loadOutlookFolders();
|
|
}, []);
|
|
|
|
const loadOutlookFolders = async () => {
|
|
try {
|
|
const folders = await engine.loadOutlookFolders();
|
|
setOutlookFolders(folders);
|
|
if (!selectedFolderId && folders.length > 0) {
|
|
setSelectedFolderId(folders[0].id);
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to load Outlook folders:", err);
|
|
// Provide a default folder option
|
|
setOutlookFolders([{ id: "default", displayName: "Kontakte (Standard)" }]);
|
|
if (!selectedFolderId) setSelectedFolderId("default");
|
|
}
|
|
};
|
|
|
|
const handleTestConnection = async () => {
|
|
setLoading(true);
|
|
setTestResult(null);
|
|
const result = await engine.testStarfaceConnection(connection);
|
|
setTestResult(result);
|
|
if (!result.success && result.message.toLowerCase().includes("fetch")) {
|
|
result.message +=
|
|
"\n\nMögliche Ursache: Das SSL-Zertifikat der Starface ist nicht vertrauenswürdig. " +
|
|
"Bitte als Administrator ausführen:\n" +
|
|
`import-cert.ps1 -StarfaceHost ${connection.host} -Port ${connection.port}`;
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleLoadAddressBooks = async () => {
|
|
setLoading(true);
|
|
setError("");
|
|
try {
|
|
const books = await engine.loadStarfaceAddressBooks(connection);
|
|
setAddressBooks(books);
|
|
if (books.length > 0 && !selectedBook) {
|
|
setSelectedBook(books[0]);
|
|
}
|
|
if (books.length === 0) {
|
|
setError("Keine Adressbücher gefunden");
|
|
}
|
|
} catch (err) {
|
|
setError(`Fehler: ${err}`);
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleSave = () => {
|
|
if (!name.trim()) {
|
|
setError("Bitte einen Profilnamen eingeben");
|
|
return;
|
|
}
|
|
if (!connection.host.trim()) {
|
|
setError("Bitte Starface-Host eingeben");
|
|
return;
|
|
}
|
|
if (!selectedBook) {
|
|
setError("Bitte ein Starface-Adressbuch auswählen");
|
|
return;
|
|
}
|
|
if (!selectedFolderId) {
|
|
setError("Bitte einen Outlook-Ordner auswählen");
|
|
return;
|
|
}
|
|
|
|
const selectedFolder = outlookFolders.find(
|
|
(f) => f.id === selectedFolderId
|
|
);
|
|
|
|
const newProfile: SyncProfile = {
|
|
id: profile?.id || pm.generateId(),
|
|
name: name.trim(),
|
|
starfaceConnection: connection,
|
|
starfaceAddressBook: selectedBook,
|
|
outlookFolderId: selectedFolderId,
|
|
outlookFolderName:
|
|
selectedFolder?.displayName || "Kontakte",
|
|
syncDirection,
|
|
lastSync: profile?.lastSync,
|
|
enabled,
|
|
};
|
|
|
|
if (isNew) {
|
|
pm.addProfile(newProfile);
|
|
} else {
|
|
pm.updateProfile(newProfile);
|
|
}
|
|
|
|
onSave();
|
|
};
|
|
|
|
const directionOptions: IDropdownOption[] = [
|
|
{ key: "both", text: "Bidirektional (↔)" },
|
|
{ key: "outlook-to-starface", text: "Outlook → Starface" },
|
|
{ key: "starface-to-outlook", text: "Starface → Outlook" },
|
|
];
|
|
|
|
const bookOptions: IDropdownOption[] = addressBooks.map((b, i) => ({
|
|
key: i.toString(),
|
|
text: b.name,
|
|
data: b,
|
|
}));
|
|
|
|
const folderOptions: IDropdownOption[] = outlookFolders.map((f) => ({
|
|
key: f.id,
|
|
text: f.displayName,
|
|
}));
|
|
|
|
return (
|
|
<div className="profile-editor">
|
|
<Stack tokens={{ childrenGap: 16 }}>
|
|
<Stack horizontal horizontalAlign="space-between" verticalAlign="center">
|
|
<Text variant="xLarge">
|
|
{isNew ? "Neues Profil" : "Profil bearbeiten"}
|
|
</Text>
|
|
<DefaultButton text="Zurück" onClick={onCancel} />
|
|
</Stack>
|
|
|
|
{error && (
|
|
<MessageBar
|
|
messageBarType={MessageBarType.error}
|
|
onDismiss={() => setError("")}
|
|
>
|
|
{error}
|
|
</MessageBar>
|
|
)}
|
|
|
|
<TextField
|
|
label="Profilname"
|
|
value={name}
|
|
onChange={(_, v) => setName(v || "")}
|
|
placeholder="z.B. Firmenkontakte Hauptanlage"
|
|
required
|
|
/>
|
|
|
|
<Text variant="large" className="section-title">
|
|
Starface-Verbindung
|
|
</Text>
|
|
|
|
<Stack horizontal tokens={{ childrenGap: 8 }}>
|
|
<Stack.Item grow>
|
|
<TextField
|
|
label="Host / IP-Adresse"
|
|
value={connection.host}
|
|
onChange={(_, v) =>
|
|
setConnection({ ...connection, host: v || "" })
|
|
}
|
|
placeholder="z.B. pbx.firma.de oder 192.168.1.100"
|
|
required
|
|
/>
|
|
</Stack.Item>
|
|
<TextField
|
|
label="Port"
|
|
value={connection.port.toString()}
|
|
onChange={(_, v) =>
|
|
setConnection({ ...connection, port: parseInt(v || "443") || 443 })
|
|
}
|
|
styles={{ root: { width: 80 } }}
|
|
/>
|
|
</Stack>
|
|
|
|
<Toggle
|
|
label="HTTPS verwenden"
|
|
checked={connection.useSsl}
|
|
onChange={(_, checked) =>
|
|
setConnection({
|
|
...connection,
|
|
useSsl: checked ?? true,
|
|
port: checked ? 443 : 80,
|
|
})
|
|
}
|
|
/>
|
|
|
|
<TextField
|
|
label="Login-ID"
|
|
value={connection.loginId}
|
|
onChange={(_, v) =>
|
|
setConnection({ ...connection, loginId: v || "" })
|
|
}
|
|
required
|
|
/>
|
|
|
|
<TextField
|
|
label="Kennwort"
|
|
type="password"
|
|
value={connection.password}
|
|
onChange={(_, v) =>
|
|
setConnection({ ...connection, password: v || "" })
|
|
}
|
|
canRevealPassword
|
|
required
|
|
/>
|
|
|
|
<Stack horizontal tokens={{ childrenGap: 8 }}>
|
|
<DefaultButton
|
|
text="Verbindung testen"
|
|
onClick={handleTestConnection}
|
|
disabled={loading || !connection.host || !connection.loginId}
|
|
/>
|
|
<PrimaryButton
|
|
text="Adressbücher laden"
|
|
onClick={handleLoadAddressBooks}
|
|
disabled={loading || !connection.host || !connection.loginId}
|
|
/>
|
|
{loading && <Spinner size={SpinnerSize.small} />}
|
|
</Stack>
|
|
|
|
{testResult && (
|
|
<MessageBar
|
|
messageBarType={
|
|
testResult.success
|
|
? MessageBarType.success
|
|
: MessageBarType.error
|
|
}
|
|
>
|
|
{testResult.message.split("\n").map((line, i) => (
|
|
<span key={i}>
|
|
{i > 0 && <br />}
|
|
{line}
|
|
</span>
|
|
))}
|
|
</MessageBar>
|
|
)}
|
|
|
|
{addressBooks.length > 0 && (
|
|
<Dropdown
|
|
label="Starface-Adressbuch"
|
|
options={bookOptions}
|
|
selectedKey={
|
|
selectedBook
|
|
? addressBooks.indexOf(selectedBook).toString()
|
|
: undefined
|
|
}
|
|
onChange={(_, option) => {
|
|
if (option?.data) setSelectedBook(option.data);
|
|
}}
|
|
required
|
|
/>
|
|
)}
|
|
|
|
<Text variant="large" className="section-title">
|
|
Outlook-Einstellungen
|
|
</Text>
|
|
|
|
<Dropdown
|
|
label="Outlook Kontakte-Ordner"
|
|
options={folderOptions}
|
|
selectedKey={selectedFolderId}
|
|
onChange={(_, option) => {
|
|
if (option) setSelectedFolderId(option.key as string);
|
|
}}
|
|
required
|
|
/>
|
|
|
|
<Dropdown
|
|
label="Synchronisationsrichtung"
|
|
options={directionOptions}
|
|
selectedKey={syncDirection}
|
|
onChange={(_, option) => {
|
|
if (option) setSyncDirection(option.key as SyncProfile["syncDirection"]);
|
|
}}
|
|
/>
|
|
|
|
<Toggle
|
|
label="Profil aktiviert"
|
|
checked={enabled}
|
|
onChange={(_, checked) => setEnabled(checked ?? true)}
|
|
/>
|
|
|
|
<Stack horizontal tokens={{ childrenGap: 8 }}>
|
|
<PrimaryButton text="Speichern" onClick={handleSave} />
|
|
<DefaultButton text="Abbrechen" onClick={onCancel} />
|
|
</Stack>
|
|
</Stack>
|
|
</div>
|
|
);
|
|
};
|