Auto-open modals from global quick actions

This commit is contained in:
Esdras Renan 2025-11-13 21:43:36 -03:00
parent 59a94744b3
commit abb29d9116
7 changed files with 70 additions and 11 deletions

View file

@ -8,9 +8,13 @@ import { fetchCompaniesByTenant, normalizeCompany } from "@/server/company-servi
export const runtime = "nodejs" export const runtime = "nodejs"
export const dynamic = "force-dynamic" export const dynamic = "force-dynamic"
export default async function AdminCompaniesPage() { export default async function AdminCompaniesPage({
searchParams,
}: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
const session = await requireStaffSession() const session = await requireStaffSession()
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
const params = await searchParams
const autoOpenCreateCompany = params.quick === "new-company"
const companies = (await fetchCompaniesByTenant(tenantId)).map(normalizeCompany) const companies = (await fetchCompaniesByTenant(tenantId)).map(normalizeCompany)
return ( return (
<AppShell <AppShell
@ -19,7 +23,11 @@ export default async function AdminCompaniesPage() {
} }
> >
<div className="mx-auto w-full max-w-7xl px-4 md:px-8 lg:px-10"> <div className="mx-auto w-full max-w-7xl px-4 md:px-8 lg:px-10">
<AdminCompaniesManager initialCompanies={companies} tenantId={tenantId} /> <AdminCompaniesManager
initialCompanies={companies}
tenantId={tenantId}
autoOpenCreate={autoOpenCreateCompany}
/>
</div> </div>
</AppShell> </AppShell>
) )

View file

@ -12,6 +12,7 @@ export default async function AdminDevicesPage({
const params = await searchParams const params = await searchParams
const companyParam = params.company const companyParam = params.company
const company = typeof companyParam === "string" ? companyParam : undefined const company = typeof companyParam === "string" ? companyParam : undefined
const autoOpenCreateDevice = params.quick === "new-device"
return ( return (
<AppShell <AppShell
header={ header={
@ -22,7 +23,11 @@ export default async function AdminDevicesPage({
} }
> >
<div className="mx-auto w-full max-w-6xl px-4 pb-12 lg:px-6"> <div className="mx-auto w-full max-w-6xl px-4 pb-12 lg:px-6">
<AdminDevicesOverview tenantId={DEFAULT_TENANT_ID} initialCompanyFilterSlug={company ?? "all"} /> <AdminDevicesOverview
tenantId={DEFAULT_TENANT_ID}
initialCompanyFilterSlug={company ?? "all"}
autoOpenCreateDevice={autoOpenCreateDevice}
/>
</div> </div>
</AppShell> </AppShell>
) )

View file

@ -9,9 +9,13 @@ import { fetchCompaniesByTenant, normalizeCompany } from "@/server/company-servi
export const runtime = "nodejs" export const runtime = "nodejs"
export const dynamic = "force-dynamic" export const dynamic = "force-dynamic"
export default async function AdminUsersPage() { export default async function AdminUsersPage({
searchParams,
}: { searchParams: Promise<Record<string, string | string[] | undefined>> }) {
const session = await requireStaffSession() const session = await requireStaffSession()
const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID
const params = await searchParams
const autoOpenCreateUser = params.quick === "new-user"
const users = await prisma.user.findMany({ const users = await prisma.user.findMany({
where: { where: {
@ -103,7 +107,12 @@ export default async function AdminUsersPage() {
} }
> >
<div className="mx-auto w-full max-w-7xl px-4 pb-12 lg:px-8"> <div className="mx-auto w-full max-w-7xl px-4 pb-12 lg:px-8">
<AdminUsersWorkspace initialAccounts={accounts} companies={companies} tenantId={tenantId} /> <AdminUsersWorkspace
initialAccounts={accounts}
companies={companies}
tenantId={tenantId}
autoOpenCreate={autoOpenCreateUser}
/>
</div> </div>
</AppShell> </AppShell>
) )

View file

@ -95,6 +95,7 @@ type LastAlertInfo = { createdAt: number; usagePct: number; threshold: number }
type Props = { type Props = {
initialCompanies: NormalizedCompany[] initialCompanies: NormalizedCompany[]
tenantId?: string | null tenantId?: string | null
autoOpenCreate?: boolean
} }
type ViewMode = "table" | "board" type ViewMode = "table" | "board"
@ -291,7 +292,7 @@ function FieldError({ error }: { error?: string }) {
return <p className="text-xs font-medium text-destructive">{error}</p> return <p className="text-xs font-medium text-destructive">{error}</p>
} }
export function AdminCompaniesManager({ initialCompanies, tenantId }: Props) { export function AdminCompaniesManager({ initialCompanies, tenantId, autoOpenCreate = false }: Props) {
const [companies, setCompanies] = useState<NormalizedCompany[]>(() => initialCompanies) const [companies, setCompanies] = useState<NormalizedCompany[]>(() => initialCompanies)
const [view, setView] = useState<ViewMode>("table") const [view, setView] = useState<ViewMode>("table")
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
@ -429,6 +430,12 @@ export function AdminCompaniesManager({ initialCompanies, tenantId }: Props) {
const cancelDelete = useCallback(() => setIsDeleting(null), []) const cancelDelete = useCallback(() => setIsDeleting(null), [])
useEffect(() => {
if (autoOpenCreate) {
openCreate()
}
}, [autoOpenCreate, openCreate])
const handleDelete = useCallback(async () => { const handleDelete = useCallback(async () => {
if (!isDeleting) return if (!isDeleting) return
try { try {

View file

@ -1268,7 +1268,15 @@ function OsIcon({ osName }: { osName?: string | null }) {
return <Monitor className="size-4 text-black" /> return <Monitor className="size-4 text-black" />
} }
export function AdminDevicesOverview({ tenantId, initialCompanyFilterSlug = "all" }: { tenantId: string; initialCompanyFilterSlug?: string }) { export function AdminDevicesOverview({
tenantId,
initialCompanyFilterSlug = "all",
autoOpenCreateDevice = false,
}: {
tenantId: string
initialCompanyFilterSlug?: string
autoOpenCreateDevice?: boolean
}) {
const { devices, isLoading } = useDevicesQuery(tenantId) const { devices, isLoading } = useDevicesQuery(tenantId)
const [q, setQ] = useState("") const [q, setQ] = useState("")
const [statusFilter, setStatusFilter] = useState<string>("all") const [statusFilter, setStatusFilter] = useState<string>("all")
@ -1555,6 +1563,12 @@ export function AdminDevicesOverview({ tenantId, initialCompanyFilterSlug = "all
setIsCreateDeviceOpen(true) setIsCreateDeviceOpen(true)
}, [selectedCompany, companyFilterSlug]) }, [selectedCompany, companyFilterSlug])
useEffect(() => {
if (autoOpenCreateDevice) {
handleOpenCreateDevice()
}
}, [autoOpenCreateDevice, handleOpenCreateDevice])
const handleCreateDevice = useCallback(async () => { const handleCreateDevice = useCallback(async () => {
if (!convexUserId) { if (!convexUserId) {
toast.error("Sincronize a sessão antes de criar dispositivos.") toast.error("Sincronize a sessão antes de criar dispositivos.")

View file

@ -95,6 +95,7 @@ type Props = {
initialAccounts: AdminAccount[] initialAccounts: AdminAccount[]
companies: NormalizedCompany[] companies: NormalizedCompany[]
tenantId: string tenantId: string
autoOpenCreate?: boolean
} }
type SectionEditorState = type SectionEditorState =
@ -164,7 +165,7 @@ function FieldError({ message }: { message?: string }) {
return <p className="text-xs font-medium text-destructive">{message}</p> return <p className="text-xs font-medium text-destructive">{message}</p>
} }
export function AdminUsersWorkspace({ initialAccounts, companies, tenantId }: Props) { export function AdminUsersWorkspace({ initialAccounts, companies, tenantId, autoOpenCreate = false }: Props) {
const [tab, setTab] = useState<"accounts" | "structure">("accounts") const [tab, setTab] = useState<"accounts" | "structure">("accounts")
return ( return (
<Tabs value={tab} onValueChange={(value) => setTab(value as typeof tab)}> <Tabs value={tab} onValueChange={(value) => setTab(value as typeof tab)}>
@ -173,7 +174,12 @@ export function AdminUsersWorkspace({ initialAccounts, companies, tenantId }: Pr
<TabsTrigger value="structure">Estrutura das empresas</TabsTrigger> <TabsTrigger value="structure">Estrutura das empresas</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="accounts"> <TabsContent value="accounts">
<AccountsTable initialAccounts={initialAccounts} companies={companies} tenantId={tenantId} /> <AccountsTable
initialAccounts={initialAccounts}
companies={companies}
tenantId={tenantId}
autoOpenCreate={autoOpenCreate}
/>
</TabsContent> </TabsContent>
<TabsContent value="structure"> <TabsContent value="structure">
<CompanyStructurePanel initialCompanies={companies} /> <CompanyStructurePanel initialCompanies={companies} />
@ -186,10 +192,12 @@ function AccountsTable({
initialAccounts, initialAccounts,
companies, companies,
tenantId, tenantId,
autoOpenCreate,
}: { }: {
initialAccounts: AdminAccount[] initialAccounts: AdminAccount[]
companies: NormalizedCompany[] companies: NormalizedCompany[]
tenantId: string tenantId: string
autoOpenCreate?: boolean
}) { }) {
const [accounts, setAccounts] = useState(initialAccounts) const [accounts, setAccounts] = useState(initialAccounts)
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
@ -212,6 +220,7 @@ function AccountsTable({
const [isResettingPassword, setIsResettingPassword] = useState(false) const [isResettingPassword, setIsResettingPassword] = useState(false)
const [passwordPreview, setPasswordPreview] = useState<string | null>(null) const [passwordPreview, setPasswordPreview] = useState<string | null>(null)
const [createDialogOpen, setCreateDialogOpen] = useState(false) const [createDialogOpen, setCreateDialogOpen] = useState(false)
const autoOpenHandledRef = useRef(false)
const [isCreatingAccount, setIsCreatingAccount] = useState(false) const [isCreatingAccount, setIsCreatingAccount] = useState(false)
const [createForm, setCreateForm] = useState<CreateAccountFormState>(() => createDefaultAccountForm()) const [createForm, setCreateForm] = useState<CreateAccountFormState>(() => createDefaultAccountForm())
@ -400,6 +409,13 @@ function AccountsTable({
setCreateForm(createDefaultAccountForm()) setCreateForm(createDefaultAccountForm())
}, []) }, [])
useEffect(() => {
if (!autoOpenCreate || autoOpenHandledRef.current) return
autoOpenHandledRef.current = true
setCreateForm(createDefaultAccountForm())
setCreateDialogOpen(true)
}, [autoOpenCreate])
useEffect(() => { useEffect(() => {
if (editAccount) { if (editAccount) {
setEditForm({ setEditForm({

View file

@ -39,7 +39,7 @@ export function GlobalQuickActions() {
label: "Adicionar empresa", label: "Adicionar empresa",
description: "Cadastrar novo cliente", description: "Cadastrar novo cliente",
icon: Building, icon: Building,
href: "/admin/companies", href: "/admin/companies?quick=new-company",
visible: Boolean(isAdmin), visible: Boolean(isAdmin),
}, },
{ {
@ -47,7 +47,7 @@ export function GlobalQuickActions() {
label: "Novo usuário", label: "Novo usuário",
description: "Gestores / colaboradores", description: "Gestores / colaboradores",
icon: UserPlus, icon: UserPlus,
href: "/admin/users", href: "/admin/users?quick=new-user",
visible: Boolean(isAdmin), visible: Boolean(isAdmin),
}, },
] ]