AdminSettings: Workforce location picker with fetch button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1dcf8bfbcb
commit
1ccaaed8be
1 changed files with 56 additions and 9 deletions
|
|
@ -33,6 +33,7 @@ const FIELD_LABELS: Record<string, string> = {
|
|||
client_id: 'Client ID', host: 'Host', port: 'Port', database: 'Database',
|
||||
graphql_endpoint: 'GraphQL Endpoint', bearer_token: 'Bearer Token',
|
||||
email: 'Email', sync_hours: 'Sync Interval (hours)', user: 'Username', pass: 'Password',
|
||||
location_id: 'Default Location',
|
||||
}
|
||||
|
||||
export function AdminSettings({ user }: { user: User }) {
|
||||
|
|
@ -96,6 +97,8 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o
|
|||
const [testing, setTesting] = useState(false)
|
||||
const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null)
|
||||
const [enabled, setEnabled] = useState(integration.enabled)
|
||||
const [wfLocations, setWfLocations] = useState<{ id: string; name: string; short_name: string | null }[]>([])
|
||||
const [fetchingLocs, setFetchingLocs] = useState(false)
|
||||
|
||||
function openForm() {
|
||||
const initial: Record<string, string> = {}
|
||||
|
|
@ -175,15 +178,59 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o
|
|||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '1rem' }}>
|
||||
|
||||
{/* Config fields (plaintext) */}
|
||||
{Object.entries(integration.config).map(([k]) => (
|
||||
<label key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
|
||||
<span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
|
||||
{FIELD_LABELS[k] ?? k}
|
||||
</span>
|
||||
<input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
|
||||
style={inputStyle} />
|
||||
</label>
|
||||
))}
|
||||
{Object.entries(integration.config).map(([k]) => {
|
||||
if (integration.slug === 'workforce' && k === 'location_id') {
|
||||
return (
|
||||
<div key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
|
||||
<span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
|
||||
Default Location
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
|
||||
<select value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
|
||||
style={{ ...inputStyle, flex: 1 }}>
|
||||
<option value="">— Select location —</option>
|
||||
{wfLocations.map(l => (
|
||||
<option key={l.id} value={l.id}>
|
||||
{l.name}{l.short_name ? ` (${l.short_name})` : ''}
|
||||
</option>
|
||||
))}
|
||||
{wfLocations.length === 0 && form[k] && (
|
||||
<option value={form[k]}>Location ID: {form[k]}</option>
|
||||
)}
|
||||
</select>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setFetchingLocs(true)
|
||||
try {
|
||||
const res = await fetch('/settings/api/integrations/workforce/locations', { credentials: 'include' })
|
||||
if (res.ok) setWfLocations(await res.json())
|
||||
} finally { setFetchingLocs(false) }
|
||||
}}
|
||||
disabled={fetchingLocs}
|
||||
style={{
|
||||
background: 'var(--body-bg)', border: '1px solid var(--card-border)',
|
||||
borderRadius: '6px', padding: '0.45rem 0.75rem', fontSize: '0.78rem',
|
||||
cursor: fetchingLocs ? 'wait' : 'pointer', color: 'var(--text-dark)',
|
||||
whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', gap: '0.35rem',
|
||||
}}
|
||||
>
|
||||
<RefreshCw size={12} strokeWidth={1.75} style={{ animation: fetchingLocs ? 'spin 1s linear infinite' : 'none' }} />
|
||||
{fetchingLocs ? 'Fetching…' : 'Fetch locations'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<label key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
|
||||
<span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
|
||||
{FIELD_LABELS[k] ?? k}
|
||||
</span>
|
||||
<input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
|
||||
style={inputStyle} />
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Secret fields (encrypted) */}
|
||||
{Object.entries(integration.secrets).map(([k, v]) => (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue