98 lines
5.9 KiB
JavaScript
98 lines
5.9 KiB
JavaScript
import { useEffect, useState } from 'react'
|
||
import { drive, message } from '@/api/client'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||
import QuotaField from '@/components/drive/QuotaField'
|
||
import {fromQuota,toQuota} from '@/lib/quota-utils'
|
||
|
||
export default function SpaceSettingsDialog({controller: c}) {
|
||
const {spaceDialog, setSpaceDialog, spaces, activeSpaceId, loadSpaces, switchSpace, setError, setNote, setManagedSpaceId} = c
|
||
const manage = spaceDialog?.mode === 'manage'
|
||
const current = spaces.find(space => space.id === spaceDialog?.spaceId)
|
||
const managed = spaces.filter(space => !space.is_private && space.permissions?.includes('create_spaces'))
|
||
const [name,setName] = useState('')
|
||
const [quota,setQuota] = useState('')
|
||
const [parent,setParent] = useState('')
|
||
const [busy,setBusy] = useState(false)
|
||
const eligibleParents = spaces.filter(space => !space.is_private && space.id !== current?.id && space.permissions?.includes('create_spaces') && !isDescendant(space, current, spaces))
|
||
const canMove = manage && !!current?.parent_id && eligibleParents.some(space => space.id !== current.parent_id)
|
||
const [localError,setLocalError] = useState('')
|
||
useEffect(() => {
|
||
setName(manage ? current?.display_name || '' : '')
|
||
setQuota(manage ? fromQuota(current?.quota_bytes) : fromQuota(managed.find(sp=>sp.id===(spaceDialog?.parentId || managed[0]?.id))?.quota_bytes))
|
||
setParent(String(manage ? current?.parent_id || '' : spaceDialog?.parentId || managed[0]?.id || ''))
|
||
setLocalError('')
|
||
}, [spaceDialog?.mode,spaceDialog?.spaceId,spaceDialog?.parentId,current?.display_name,current?.quota_bytes,current?.parent_id,managed[0]?.id])
|
||
|
||
async function save(event) {
|
||
event.preventDefault()
|
||
setBusy(true);setLocalError('')
|
||
try {
|
||
if (manage && !current) throw new Error('Sous-espace introuvable. Actualise la page.')
|
||
const data={display_name:name.trim()}
|
||
// Managing an assigned/shared space must never resubmit its quota.
|
||
if (!manage || current?.can_edit_quota) data.quota_bytes=toQuota(quota)
|
||
if (manage && canMove && parent && Number(parent) !== current.parent_id) data.parent_space = Number(parent)
|
||
const result=manage ? await drive.editSpace(current.id,data) : await drive.createSpace({...data,parent_space:Number(parent)})
|
||
await loadSpaces()
|
||
if (!manage) switchSpace(result.id)
|
||
if (manage && data.parent_space) await c.reloadStats()
|
||
setNote(manage ? 'Espace modifié.' : 'Espace créé.')
|
||
setSpaceDialog(null)
|
||
} catch(exception) {setLocalError(exception?.response ? message(exception) : exception.message || message(exception))}
|
||
finally {setBusy(false)}
|
||
}
|
||
function members() {
|
||
if (!current) return
|
||
setManagedSpaceId(current.id)
|
||
setSpaceDialog(null)
|
||
|
||
}
|
||
return <Dialog open={!!spaceDialog && ['manage','create'].includes(spaceDialog.mode)} onOpenChange={open=>{if(!open&&!busy)setSpaceDialog(null)}}>
|
||
<DialogContent className="sm:max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>{manage ? `Gérer l’espace « ${current?.display_name || 'introuvable'} »` : 'Créer un espace partagé'}</DialogTitle>
|
||
<DialogDescription className="sr-only">{manage ? 'Paramètres de cet espace' : 'Paramètres du nouvel espace'}</DialogDescription>
|
||
</DialogHeader>
|
||
<form onSubmit={save} className="space-y-4">
|
||
{!manage && <div className="space-y-2"><Label htmlFor="space-parent">Espace parent</Label>
|
||
<select id="space-parent" value={parent} onChange={e=>{setParent(e.target.value);setQuota(fromQuota(managed.find(sp=>sp.id===Number(e.target.value))?.quota_bytes))}} required
|
||
className="h-10 w-full rounded-md border bg-background px-3 text-sm">
|
||
{managed.map(space=><option key={space.id} value={space.id}>{space.display_name}</option>)}
|
||
</select></div>}
|
||
{canMove && <div className="space-y-2"><Label htmlFor="space-new-parent">Espace parent</Label>
|
||
<select id="space-new-parent" value={parent} disabled={busy} onChange={e=>setParent(e.target.value)}
|
||
className="h-10 w-full rounded-md border bg-background px-3 text-sm">
|
||
{current?.parent_id && !eligibleParents.some(space => space.id === current.parent_id) &&
|
||
<option value={current.parent_id}>{spaces.find(space => space.id === current.parent_id)?.display_name || 'Parent actuel'}</option>}
|
||
{eligibleParents.map(space=><option key={space.id} value={space.id}>{space.display_name}</option>)}
|
||
</select>
|
||
</div>}
|
||
<div className="space-y-2"><Label htmlFor="space-name">Nom affiché</Label>
|
||
<Input id="space-name" value={name} onChange={e=>setName(e.target.value)} maxLength={120} required placeholder="Ex. Équipe projet" /></div>
|
||
{(!manage || current?.can_edit_quota) &&
|
||
<QuotaField id="space-quota" value={quota} onChange={setQuota} disabled={busy} />}
|
||
{localError && <p role="alert" className="text-sm text-destructive">{localError}</p>}
|
||
<DialogFooter className="gap-2 sm:justify-between">
|
||
{manage && current?.can_invite && <Button type="button" variant="outline" onClick={members}>Gérer les accès</Button>}
|
||
<Button disabled={busy||!name.trim()||(manage&&!current)||(!manage&&!parent)} type="submit">{busy ? 'Enregistrement…' : manage ? 'Enregistrer' : 'Créer l’espace'}</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
}
|
||
|
||
function isDescendant(candidate, ancestor, spaces) {
|
||
if (!ancestor) return false
|
||
let current = candidate
|
||
const visited = new Set()
|
||
while (current && !visited.has(current.id)) {
|
||
if (current.id === ancestor.id) return true
|
||
visited.add(current.id)
|
||
current = spaces.find(space => space.id === current.parent_id)
|
||
}
|
||
return false
|
||
}
|