ultisuite-client/components/gmail/contacts-page/contacts-bulk-edit-dialog.tsx
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- Updated .env.example to include configuration for OnlyOffice Document Server.
- Modified the workspace configuration to remove the drive-suite path.
- Adjusted TypeScript environment imports for consistency.
- Enhanced Next.js configuration to disable canvas in Webpack.
- Updated package.json to include new dependencies for OnlyOffice and PDF.js.
- Added global styles for OnlyOffice theme integration in the CSS.
- Created new layout and page components for the Drive feature, including public sharing and editing functionalities.
- Updated metadata handling across various layouts to reflect the new app structure.
2026-06-07 15:49:21 +02:00

193 lines
5.5 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import type { FullContact } from "@/lib/contacts/types"
import {
CONTACT_BULK_EDIT_FIELDS,
collectBulkFieldSuggestions,
getContactBulkFieldValue,
type ContactBulkEditField,
} from "@/lib/contacts/bulk-edit-fields"
import {
CONTACTS_FIELD_CLASS,
CONTACTS_MENU_SURFACE_CLASS,
CONTACTS_MUTED_TEXT,
CONTACTS_PRIMARY_BTN_CLASS,
} from "@/lib/contacts-chrome-classes"
import { cn } from "@/lib/utils"
interface ContactsBulkEditDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
contacts: FullContact[]
onApply: (field: ContactBulkEditField, value: string) => void
isApplying?: boolean
}
export function ContactsBulkEditDialog({
open,
onOpenChange,
contacts,
onApply,
isApplying = false,
}: ContactsBulkEditDialogProps) {
const [field, setField] = useState<ContactBulkEditField>("company")
const [value, setValue] = useState("")
const suggestions = useMemo(
() => collectBulkFieldSuggestions(contacts, field),
[contacts, field],
)
const mixedValues = useMemo(() => {
const values = new Set(
contacts.map((c) => getContactBulkFieldValue(c, field)).filter(Boolean),
)
return values.size > 1
}, [contacts, field])
useEffect(() => {
if (!open) {
setField("company")
setValue("")
return
}
if (suggestions.length === 1) {
setValue(suggestions[0]!)
} else {
setValue("")
}
}, [open, field, suggestions])
const isNotes = field === "notes"
const canApply = contacts.length > 0
function handleApply() {
if (!canApply) return
onApply(field, value)
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Édition de masse</DialogTitle>
<p className={cn("text-sm", CONTACTS_MUTED_TEXT)}>
{contacts.length} contact{contacts.length > 1 ? "s" : ""} sélectionné
{contacts.length > 1 ? "s" : ""}
</p>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Propriété
</label>
<Select
value={field}
onValueChange={(v) => setField(v as ContactBulkEditField)}
>
<SelectTrigger className={CONTACTS_FIELD_CLASS}>
<SelectValue />
</SelectTrigger>
<SelectContent data-contacts-menu-surface className={CONTACTS_MENU_SURFACE_CLASS}>
{CONTACT_BULK_EDIT_FIELDS.map((f) => (
<SelectItem key={f.id} value={f.id}>
{f.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground">
Nouvelle valeur
</label>
{isNotes ? (
<Textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={
mixedValues ? "Valeurs mixtes dans la sélection…" : "Notes…"
}
className={cn(CONTACTS_FIELD_CLASS, "min-h-24 resize-y")}
/>
) : (
<Input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={
mixedValues
? "Valeurs mixtes dans la sélection…"
: `Nouvelle valeur…`
}
className={CONTACTS_FIELD_CLASS}
/>
)}
</div>
{suggestions.length > 0 && (
<div className="space-y-2">
<p className={cn("text-xs", CONTACTS_MUTED_TEXT)}>
Valeurs présentes dans la sélection
</p>
<div className="flex flex-wrap gap-1.5">
{suggestions.map((suggestion) => (
<button
key={suggestion}
type="button"
onClick={() => setValue(suggestion)}
className={cn(
"max-w-full truncate rounded-full border border-border px-2.5 py-1 text-xs text-foreground transition-colors hover:bg-muted",
value === suggestion && "border-primary bg-primary/10",
)}
>
{suggestion}
</button>
))}
</div>
</div>
)}
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Annuler
</Button>
<Button
type="button"
className={CONTACTS_PRIMARY_BTN_CLASS}
disabled={!canApply || isApplying}
onClick={handleApply}
>
Appliquer
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}