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',
|
client_id: 'Client ID', host: 'Host', port: 'Port', database: 'Database',
|
||||||
graphql_endpoint: 'GraphQL Endpoint', bearer_token: 'Bearer Token',
|
graphql_endpoint: 'GraphQL Endpoint', bearer_token: 'Bearer Token',
|
||||||
email: 'Email', sync_hours: 'Sync Interval (hours)', user: 'Username', pass: 'Password',
|
email: 'Email', sync_hours: 'Sync Interval (hours)', user: 'Username', pass: 'Password',
|
||||||
|
location_id: 'Default Location',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdminSettings({ user }: { user: User }) {
|
export function AdminSettings({ user }: { user: User }) {
|
||||||
|
|
@ -96,6 +97,8 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o
|
||||||
const [testing, setTesting] = useState(false)
|
const [testing, setTesting] = useState(false)
|
||||||
const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null)
|
const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null)
|
||||||
const [enabled, setEnabled] = useState(integration.enabled)
|
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() {
|
function openForm() {
|
||||||
const initial: Record<string, string> = {}
|
const initial: Record<string, string> = {}
|
||||||
|
|
@ -175,7 +178,50 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '1rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '1rem' }}>
|
||||||
|
|
||||||
{/* Config fields (plaintext) */}
|
{/* Config fields (plaintext) */}
|
||||||
{Object.entries(integration.config).map(([k]) => (
|
{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' }}>
|
<label key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
|
||||||
<span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
|
<span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
|
||||||
{FIELD_LABELS[k] ?? k}
|
{FIELD_LABELS[k] ?? k}
|
||||||
|
|
@ -183,7 +229,8 @@ function IntegrationCard({ integration, onSaved }: { integration: Integration; o
|
||||||
<input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
|
<input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
|
||||||
style={inputStyle} />
|
style={inputStyle} />
|
||||||
</label>
|
</label>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
{/* Secret fields (encrypted) */}
|
{/* Secret fields (encrypted) */}
|
||||||
{Object.entries(integration.secrets).map(([k, v]) => (
|
{Object.entries(integration.secrets).map(([k, v]) => (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue