diff --git a/dashboard/src/components/entity-table/index.tsx b/dashboard/src/components/entity-table/index.tsx
@@ -1,25 +1,25 @@
-import { FC, useEffect, useState } from "react";
+import { type FC, useMemo, useState } from "react";
import { Button, DataTableViewOptions } from "@marzneshin/components";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { EntityTableContext } from "./entity-table-provider";
-import { EntityDataTable } from "./table"
-import { DataTablePagination } from "./table-pagination"
+import { EntityDataTable } from "./table";
+import { DataTablePagination } from "./table-pagination";
import { TableFiltering } from "./table-filtering";
import {
- UseRowSelectionReturn,
- UseDialogProps,
+ type UseRowSelectionReturn,
+ type UseDialogProps,
useFiltering,
usePagination,
- FetchEntityReturn,
+ type FetchEntityReturn,
useEntityTable,
useVisibility,
useSorting,
- SortableQueryKey,
- QueryKey,
- EntityQueryKeyType,
+ type SortableQueryKey,
+ type QueryKey,
+ type EntityQueryKeyType,
} from "./hooks";
-import { useDialog } from "@marzneshin/hooks"
+import { useDialog } from "@marzneshin/hooks";
interface EntityTableProps<T> {
fetchEntity: ({ queryKey }: EntityQueryKeyType) => FetchEntityReturn<T>;
@@ -28,9 +28,9 @@ interface EntityTableProps<T> {
SettingsDialog: FC<UseDialogProps<T | any>>;
columnsFn: any;
filteredColumn: string;
- entityKey: string
- rowSelection?: UseRowSelectionReturn
- manualSorting?: boolean
+ entityKey: string;
+ rowSelection?: UseRowSelectionReturn;
+ manualSorting?: boolean;
}
export function EntityTable<T>({
@@ -42,7 +42,7 @@ export function EntityTable<T>({
filteredColumn,
rowSelection,
entityKey,
- manualSorting = false
+ manualSorting = false,
}: EntityTableProps<T>) {
const [mutationDialogOpen, setMutationDialogOpen] = useDialog();
const [deleteDialogOpen, setDeleteDialogOpen] = useDialog();
@@ -69,10 +69,10 @@ export function EntityTable<T>({
setSettingsDialogOpen(true);
};
- const { t } = useTranslation()
- const filtering = useFiltering({ column: filteredColumn })
- const sorting = useSorting()
- const visibility = useVisibility()
+ const { t } = useTranslation();
+ const filtering = useFiltering({ column: filteredColumn });
+ const sorting = useSorting();
+ const visibility = useVisibility();
const { onPaginationChange, pageIndex, pageSize } = usePagination();
const sortedQuery: SortableQueryKey = [
entityKey,
@@ -80,16 +80,22 @@ export function EntityTable<T>({
pageSize,
filtering.columnFilters,
sorting.sorting[0]?.id ? sorting.sorting[0].id : "created_at",
- sorting.sorting[0]?.desc]
- const query: QueryKey = [entityKey, pageIndex, pageSize, filtering.columnFilters]
+ sorting.sorting[0]?.desc,
+ ];
+ const query: QueryKey = [
+ entityKey,
+ pageIndex,
+ pageSize,
+ filtering.columnFilters,
+ ];
const { data, isLoading } = useQuery({
queryFn: fetchEntity,
queryKey: manualSorting ? sortedQuery : query,
- initialData: { entity: [], pageCount: 1 }
+ initialData: { entity: [], pageCount: 1 },
});
- const columns = columnsFn({ onEdit, onDelete, onOpen })
+ const columns = columnsFn({ onEdit, onDelete, onOpen });
const table = useEntityTable({
data,
columns,
@@ -98,14 +104,17 @@ export function EntityTable<T>({
rowSelection,
visibility,
sorting,
- onPaginationChange
- })
+ onPaginationChange,
+ });
- useEffect(() => table.setPageIndex(1), [filtering.columnFilters, table])
+ const contextValue = useMemo(() => (
+ { table, data: data.entity, filtering, isLoading }
+ ), [table, data.entity, filtering, isLoading])
- // TODO: Move the selectedEntity to context
return (
- <EntityTableContext.Provider value={{ table, data: data.entity, filtering, isLoading }}>
+ <EntityTableContext.Provider
+ value={contextValue}
+ >
<SettingsDialog
open={settingsDialogOpen}
onOpenChange={setSettingsDialogOpen}
@@ -126,18 +135,13 @@ export function EntityTable<T>({
<TableFiltering />
<DataTableViewOptions table={table} />
{onCreate && (
- <Button
- aria-label={`create-${entityKey}`}
- onClick={onCreate}>
- {t('create')}
+ <Button aria-label={`create-${entityKey}`} onClick={onCreate}>
+ {t("create")}
</Button>
)}
</div>
<div className="w-full rounded-md border">
- <EntityDataTable
- columns={columns}
- onRowClick={onOpen}
- />
+ <EntityDataTable columns={columns} onRowClick={onOpen} />
<DataTablePagination />
</div>
</div>
@@ -145,4 +149,4 @@ export function EntityTable<T>({
);
}
-export * from './hooks'
+export * from "./hooks";
diff --git a/dashboard/src/features/services/dialogs/index.ts b/dashboard/src/features/services/dialogs/index.ts
@@ -1,2 +1,2 @@
-export * from './delete-confirmation.dialog'
-export * from './mutation.dialog'
+export * from "./delete-confirmation.dialog";
+export * from "./mutation";
diff --git a/dashboard/src/features/services/dialogs/mutation.dialog.tsx b/dashboard/src/features/services/dialogs/mutation.dialog.tsx
@@ -1,170 +0,0 @@
-import {
- DialogTitle,
- DialogContent,
- Dialog,
- DialogHeader,
- Form,
- FormItem,
- FormControl,
- FormMessage,
- FormLabel,
- Input,
- FormField,
- Button,
- Checkbox,
- Badge,
-} from "@marzneshin/components";
-import { FC } from "react";
-import {
- ServiceType,
- useServicesCreationMutation,
- useServicesUpdateMutation,
-} from "@marzneshin/features/services";
-import { useInboundsQuery } from "@marzneshin/features/inbounds";
-import { useTranslation } from "react-i18next";
-import { z } from "zod";
-import { cn } from "@marzneshin/utils";
-import { Box, GlobeLock } from "lucide-react";
-import { useMutationDialog } from "@marzneshin/hooks";
-
-export const ServiceSchema = z.object({
- id: z.number().optional(),
- inbounds: z.array(z.number()).refine((value) => value.some((item) => item), {
- message: "You have to select at least one inbound.",
- }),
- name: z.string().trim().min(1),
-});
-type ServiceCreateType = z.infer<typeof ServiceSchema>;
-
-interface MutationDialogProps {
- entity: ServiceType | null;
- open: boolean;
- onOpenChange: (state: boolean) => void;
-}
-
-const getDefaultValues = (): ServiceCreateType => ({
- name: "",
- inbounds: [],
-});
-
-export const MutationDialog: FC<MutationDialogProps> = ({
- entity,
- open,
- onOpenChange,
-}) => {
- const isEditing = entity !== null;
- const updateMutation = useServicesUpdateMutation();
- const createMutation = useServicesCreationMutation();
- const { data: inbounds } = useInboundsQuery();
- const { form, handleSubmit } = useMutationDialog({
- entity,
- updateMutation,
- createMutation,
- getDefaultValue: getDefaultValues,
- schema: ServiceSchema,
- onOpenChange,
- });
- const { t } = useTranslation();
- return (
- <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle className="text-primary">
- {isEditing
- ? t("page.services.dialogs.edition.title")
- : t("page.services.dialogs.creation.title")}
- </DialogTitle>
- </DialogHeader>
- <Form {...form}>
- <form onSubmit={handleSubmit} className="h-full">
- <FormField
- control={form.control}
- name="name"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t("name")}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={form.control}
- name="inbounds"
- render={() => (
- <FormItem>
- <FormLabel>{t("inbounds")}</FormLabel>
- {inbounds.map((inbound) => (
- <FormField
- key={inbound.id}
- control={form.control}
- name="inbounds"
- render={({ field }) => (
- <FormItem key={inbound.id}>
- <FormControl>
- <div
- className={cn(
- "flex flex-row items-center p-3 space-y-0 space-x-3 rounded-md border",
- {
- "bg-secondary": field.value?.includes(
- inbound.id,
- ),
- },
- )}
- >
- <Checkbox
- checked={field.value?.includes(inbound.id)}
- onCheckedChange={(checked) => {
- return checked
- ? field.onChange([
- ...field.value,
- inbound.id,
- ])
- : field.onChange(
- field.value?.filter(
- (value: number) =>
- value !== inbound.id,
- ),
- );
- }}
- />
- <FormLabel className="flex flex-row justify-between items-center w-full">
- {inbound.tag}
- <div className="flex gap-2">
- <Badge className="py-1 px-2">
- {" "}
- <GlobeLock className="mr-1 w-4" />{" "}
- {inbound.protocol}{" "}
- </Badge>
- <Badge className="py-1 px-2">
- {" "}
- <Box className="mr-1 w-4" />{" "}
- {inbound.node.name}{" "}
- </Badge>
- </div>
- </FormLabel>
- </div>
- </FormControl>
- </FormItem>
- )}
- />
- ))}
- <FormMessage />
- </FormItem>
- )}
- />
- <Button
- className="mt-3 w-full font-semibold"
- type="submit"
- disabled={form.formState.isSubmitting}
- >
- {t("submit")}
- </Button>
- </form>
- </Form>
- </DialogContent>
- </Dialog>
- );
-};
diff --git a/dashboard/src/features/services/dialogs/mutation/fields/inbounds.tsx b/dashboard/src/features/services/dialogs/mutation/fields/inbounds.tsx
@@ -0,0 +1,97 @@
+import {
+ FormItem,
+ FormControl,
+ FormMessage,
+ FormLabel,
+ FormField,
+ Checkbox,
+ Badge,
+} from "@marzneshin/components";
+import {
+ useInboundsQuery,
+ type InboundType,
+} from "@marzneshin/features/inbounds";
+import {
+ useFormContext,
+ type FieldValues,
+ type ControllerRenderProps,
+} from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { cn } from "@marzneshin/utils";
+import { Box, GlobeLock } from "lucide-react";
+
+export const InboundCard = ({
+ inbound,
+ field,
+}: {
+ inbound: InboundType;
+ field: ControllerRenderProps<FieldValues, "inbounds">;
+}) => {
+ return (
+ <div
+ className={cn(
+ "flex flex-row items-center p-3 space-y-0 space-x-3 rounded-md border",
+ {
+ "bg-secondary": field.value?.includes(inbound.id),
+ },
+ )}
+ >
+ <Checkbox
+ checked={field.value?.includes(inbound.id)}
+ onCheckedChange={(checked) => {
+ return checked
+ ? field.onChange([...field.value, inbound.id])
+ : field.onChange(
+ field.value?.filter((value: number) => value !== inbound.id),
+ );
+ }}
+ />
+ <FormLabel className="flex flex-row justify-between items-center w-full">
+ {inbound.tag}
+ <div className="flex gap-2">
+ <Badge className="py-1 px-2">
+ {" "}
+ <GlobeLock className="mr-1 w-4" /> {inbound.protocol}{" "}
+ </Badge>
+ <Badge className="py-1 px-2">
+ {" "}
+ <Box className="mr-1 w-4" /> {inbound.node.name}{" "}
+ </Badge>
+ </div>
+ </FormLabel>
+ </div>
+ );
+};
+
+export const InboundsField = () => {
+ const { data: inbounds } = useInboundsQuery();
+ const form = useFormContext();
+ const { t } = useTranslation();
+
+ return (
+ <FormField
+ control={form.control}
+ name="inbounds"
+ render={() => (
+ <FormItem>
+ <FormLabel>{t("inbounds")}</FormLabel>
+ {inbounds.map((inbound) => (
+ <FormField
+ key={inbound.id}
+ control={form.control}
+ name="inbounds"
+ render={({ field }) => (
+ <FormItem key={inbound.id}>
+ <FormControl>
+ <InboundCard inbound={inbound} field={field} />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ ))}
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ );
+};
diff --git a/dashboard/src/features/services/dialogs/mutation/fields/index.ts b/dashboard/src/features/services/dialogs/mutation/fields/index.ts
@@ -0,0 +1,2 @@
+export * from "./name";
+export * from "./inbounds";
diff --git a/dashboard/src/features/services/dialogs/mutation/fields/name.tsx b/dashboard/src/features/services/dialogs/mutation/fields/name.tsx
@@ -0,0 +1,30 @@
+import {
+ FormItem,
+ FormControl,
+ FormMessage,
+ FormLabel,
+ Input,
+ FormField,
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const NameField = () => {
+ const form = useFormContext();
+ const { t } = useTranslation();
+ return (
+ <FormField
+ control={form.control}
+ name="name"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t("name")}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ );
+};
diff --git a/dashboard/src/features/services/dialogs/mutation/index.tsx b/dashboard/src/features/services/dialogs/mutation/index.tsx
@@ -0,0 +1,83 @@
+import {
+ DialogTitle,
+ DialogContent,
+ Dialog,
+ DialogHeader,
+ Form,
+ Button,
+} from "@marzneshin/components";
+import type { FC } from "react";
+import {
+ type ServiceType,
+ useServicesCreationMutation,
+ useServicesUpdateMutation,
+} from "@marzneshin/features/services";
+import { useTranslation } from "react-i18next";
+import { z } from "zod";
+import { useMutationDialog } from "@marzneshin/hooks";
+import { NameField, InboundsField } from "./fields";
+
+export const ServiceSchema = z.object({
+ id: z.number().optional(),
+ inbounds: z.array(z.number()).refine((value) => value.some((item) => item), {
+ message: "You have to select at least one inbound.",
+ }),
+ name: z.string().trim().min(1),
+});
+type ServiceCreateType = z.infer<typeof ServiceSchema>;
+
+interface MutationDialogProps {
+ entity: ServiceType | null;
+ open: boolean;
+ onOpenChange: (state: boolean) => void;
+}
+
+const getDefaultValues = (): ServiceCreateType => ({
+ name: "",
+ inbounds: [],
+});
+
+export const MutationDialog: FC<MutationDialogProps> = ({
+ entity,
+ open,
+ onOpenChange,
+}) => {
+ const isEditing = entity !== null;
+ const updateMutation = useServicesUpdateMutation();
+ const createMutation = useServicesCreationMutation();
+ const { form, handleSubmit } = useMutationDialog({
+ entity,
+ updateMutation,
+ createMutation,
+ getDefaultValue: getDefaultValues,
+ schema: ServiceSchema,
+ onOpenChange,
+ });
+ const { t } = useTranslation();
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle className="text-primary">
+ {isEditing
+ ? t("page.services.dialogs.edition.title")
+ : t("page.services.dialogs.creation.title")}
+ </DialogTitle>
+ </DialogHeader>
+ <Form {...form}>
+ <form onSubmit={handleSubmit} className="h-full">
+ <NameField />
+ <InboundsField />
+ <Button
+ className="mt-3 w-full font-semibold"
+ type="submit"
+ disabled={form.formState.isSubmitting}
+ >
+ {t("submit")}
+ </Button>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ );
+};
diff --git a/dashboard/src/features/services/tables/inbounds/index.tsx b/dashboard/src/features/services/tables/inbounds/index.tsx
@@ -1,58 +1,61 @@
-
-import { FC, useCallback, useEffect, useState } from "react";
-import {
- useInboundsQuery,
-} from '@marzneshin/features/inbounds';
+import { type FC, useCallback, useEffect, useState } from "react";
+import { useInboundsQuery } from "@marzneshin/features/inbounds";
import { Button, DataTable } from "@marzneshin/components";
import { columns } from "./columns";
-import { ServiceType, useServicesUpdateMutation } from "../..";
+import { type ServiceType, useServicesUpdateMutation } from "../..";
import { useTranslation } from "react-i18next";
-import { RowSelectionState } from "@tanstack/react-table";
+import type { RowSelectionState } from "@tanstack/react-table";
interface ServiceInboundsTableProps {
- service: ServiceType
+ service: ServiceType;
}
-export const ServiceInboundsTable: FC<ServiceInboundsTableProps> = ({ service }) => {
+export const ServiceInboundsTable: FC<ServiceInboundsTableProps> = ({
+ service,
+}) => {
const { mutate: updateService } = useServicesUpdateMutation();
const { data } = useInboundsQuery();
- const [selectedInbound, setSelectedInbound] = useState<{ [key: number]: boolean }>({})
- const { t } = useTranslation()
+ const [selectedInbound, setSelectedInbound] = useState<{
+ [key: number]: boolean;
+ }>({});
+ const { t } = useTranslation();
useEffect(() => {
setSelectedInbound((prevSelected) => {
const updatedSelected: RowSelectionState = { ...prevSelected };
- service.inbounds.forEach((inboundId) => {
- data.forEach((fetchedInbound, i) => {
+ for (const inboundId of service.inbounds) {
+ for (const [i, fetchedInbound] of data.entries()) {
if (fetchedInbound.id === inboundId) {
updatedSelected[i] = true;
}
- });
- });
+ }
+ }
return updatedSelected;
});
- }, [data, service.inbounds])
+ }, [data, service.inbounds]);
const handleApply = useCallback(() => {
const selectedInboundIds = Object.keys(selectedInbound)
- .filter(key => selectedInbound[parseInt(key)])
- .map(key => data[parseInt(key)].id);
+ .filter((key) => selectedInbound[Number.parseInt(key)])
+ .map((key) => data[Number.parseInt(key)].id);
updateService({ ...service, inbounds: selectedInboundIds });
}, [data, selectedInbound, service, updateService]);
- const disabled = (Object.keys(selectedInbound).length < 1) ? true : false
+ const disabled = Object.keys(selectedInbound).length < 1;
return (
<div className="flex flex-col gap-4">
<DataTable
columns={columns}
data={data}
- filteredColumn='tag'
+ filteredColumn="tag"
selectedRow={selectedInbound}
setSelectedRow={setSelectedInbound}
/>
- <Button onClick={handleApply} disabled={disabled}>{t('apply')}</Button>
+ <Button onClick={handleApply} disabled={disabled}>
+ {t("apply")}
+ </Button>
</div>
- )
-}
+ );
+};
diff --git a/dashboard/src/features/users/dialogs/mutation/fields/services.tsx b/dashboard/src/features/users/dialogs/mutation/fields/services.tsx
@@ -1,71 +1,90 @@
import {
- Checkbox,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
- ScrollArea,
+ Checkbox,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ ScrollArea,
} from "@marzneshin/components";
-import { useServicesQuery } from "@marzneshin/features/services";
+import {
+ useServicesQuery,
+ type ServiceType,
+} from "@marzneshin/features/services";
import { ServiceCard } from "@marzneshin/features/users";
import { cn } from "@marzneshin/utils";
import type { FC } from "react";
-import { useFormContext } from "react-hook-form";
+import {
+ useFormContext,
+ type FieldValues,
+ type ControllerRenderProps,
+} from "react-hook-form";
import { useTranslation } from "react-i18next";
+interface ServiceCheckboxCardProps {
+ service: ServiceType;
+ field: ControllerRenderProps<FieldValues, "services">;
+}
+
+const ServiceCheckboxCard: FC<ServiceCheckboxCardProps> = ({
+ service,
+ field,
+}) => {
+ return (
+ <div
+ className={cn(
+ "flex mb-2 flex-row gap-2 items-center p-3 rounded-md border",
+ { "bg-secondary": field.value?.includes(service.id) },
+ )}
+ >
+ <Checkbox
+ checked={field.value?.includes(service.id)}
+ onCheckedChange={(checked) => {
+ return checked
+ ? field.onChange([...field.value, service.id])
+ : field.onChange(
+ field.value?.filter((value: number) => value !== service.id),
+ );
+ }}
+ />
+ <FormLabel className="flex flex-row justify-between items-center w-full">
+ <ServiceCard service={service} />
+ </FormLabel>
+ </div>
+ );
+};
+
export const ServicesField: FC = () => {
- const form = useFormContext();
- const { data } = useServicesQuery({ page: 1, size: 100 });
- const { t } = useTranslation();
+ const form = useFormContext();
+ const { data } = useServicesQuery({ page: 1, size: 100 });
+ const { t } = useTranslation();
- return (
- <FormField
- control={form.control}
- name="services"
- render={() => (
- <FormItem>
- <FormLabel>{t("services")}</FormLabel>
- <ScrollArea className="flex flex-col justify-start px-1 h-full max-h-[20rem]">
- {data.entity.map((service) => (
- <FormField
- key={service.id}
- control={form.control}
- name="services"
- render={({ field }) => (
- <FormItem key={service.id}>
- <FormControl>
- <div
- className={cn(
- "flex mb-2 flex-row gap-2 items-center p-3 rounded-md border",
- { "bg-secondary": field.value?.includes(service.id) },
- )}
- >
- <Checkbox
- checked={field.value?.includes(service.id)}
- onCheckedChange={(checked) => {
- return checked
- ? field.onChange([...field.value, service.id])
- : field.onChange(
- field.value?.filter(
- (value: number) => value !== service.id,
- ),
- );
- }}
- />
- <FormLabel className="flex flex-row justify-between items-center w-full">
- <ServiceCard service={service} />
- </FormLabel>
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- ))}
- </ScrollArea>
- </FormItem>
- )}
- />
- );
+ return (
+ <FormField
+ control={form.control}
+ name="services"
+ render={() => (
+ <FormItem>
+ <FormLabel>{t("services")}</FormLabel>
+ <ScrollArea className="flex flex-col justify-start px-1 h-full max-h-[20rem]">
+ {data.entity.map((service) => (
+ <FormField
+ key={service.id}
+ control={form.control}
+ name="services"
+ render={({ field }) => (
+ <FormItem key={service.id}>
+ <FormControl>
+ <ServiceCheckboxCard service={service} field={field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ ))}
+ </ScrollArea>
+ </FormItem>
+ )}
+ />
+ );
};
diff --git a/dashboard/src/features/users/dialogs/mutation/fields/username.tsx b/dashboard/src/features/users/dialogs/mutation/fields/username.tsx
@@ -4,24 +4,25 @@ import {
FormItem,
Input,
FormLabel,
- FormMessage
-} from '@marzneshin/components';
-import { FC, InputHTMLAttributes } from 'react'
-import { useFormContext } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
+ FormMessage,
+} from "@marzneshin/components";
+import type { FC, InputHTMLAttributes } from "react";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
-interface UsernameFieldProps { }
+export const UsernameField: FC<InputHTMLAttributes<HTMLElement>> = ({
+ disabled,
+}) => {
+ const form = useFormContext();
+ const { t } = useTranslation();
-export const UsernameField: FC<UsernameFieldProps & InputHTMLAttributes<HTMLElement>> = ({ disabled }) => {
- const form = useFormContext()
- const { t } = useTranslation()
return (
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
- <FormLabel>{t('username')}</FormLabel>
+ <FormLabel>{t("username")}</FormLabel>
<FormControl>
<Input disabled={disabled} {...field} />
</FormControl>
@@ -30,4 +31,4 @@ export const UsernameField: FC<UsernameFieldProps & InputHTMLAttributes<HTMLElem
)}
/>
);
-}
+};
diff --git a/dashboard/src/features/users/tables/services/index.tsx b/dashboard/src/features/users/tables/services/index.tsx
@@ -1,63 +1,64 @@
-
-
-import { FC, useCallback, useEffect, useState } from "react";
-import {
-} from '@marzneshin/features/inbounds';
+import { type FC, useCallback, useEffect, useState } from "react";
+import { } from "@marzneshin/features/inbounds";
import { Button, DataTable } from "@marzneshin/components";
import { columns } from "./columns";
-import { UserType, useUsersUpdateMutation } from "@marzneshin/features/users";
-import { useTranslation } from "react-i18next";
-import { RowSelectionState } from "@tanstack/react-table";
import {
- ServiceType,
- useServicesQuery,
-} from "@marzneshin/features/services";
+ type UserType,
+ useUsersUpdateMutation,
+} from "@marzneshin/features/users";
+import { useTranslation } from "react-i18next";
+import type { RowSelectionState } from "@tanstack/react-table";
+import { useServicesQuery } from "@marzneshin/features/services";
interface UserServicesTableProps {
- user: UserType
+ user: UserType;
}
-// TODO: Implement pagination for row selectable with appliedable action table
+// TODO: #322 Implement pagination for row selectable with appliedable action table
export const UserServicesTable: FC<UserServicesTableProps> = ({ user }) => {
const { mutate: updateUser } = useUsersUpdateMutation();
const { data } = useServicesQuery({ page: 1, size: 100 });
- const [selectedService, setSelectedService] = useState<{ [key: number]: boolean }>({})
- const { t } = useTranslation()
+ const [selectedService, setSelectedService] = useState<{
+ [key: number]: boolean;
+ }>({});
+ const { t } = useTranslation();
useEffect(() => {
setSelectedService((prevSelected) => {
const updatedSelected: RowSelectionState = { ...prevSelected };
- user.services.forEach((serviceId) => {
- data.entity.forEach((fetchedService: ServiceType, i) => {
+ for (const serviceId of user.services) {
+ for (const [i, fetchedService] of data.entity.entries()) {
if (fetchedService.id === serviceId) {
updatedSelected[i] = true;
}
- });
- });
+ }
+ }
return updatedSelected;
});
- }, [data, user.services])
+ }, [data, user.services]);
const handleApply = useCallback(() => {
const selectedServiceIds = Object.keys(selectedService)
- .filter(key => selectedService[parseInt(key)])
- .map(key => data.entity[parseInt(key)].id);
+ .filter((key) => selectedService[Number.parseInt(key)])
+ .map((key) => data.entity[Number.parseInt(key)].id);
updateUser({ ...user, services: selectedServiceIds });
}, [data, selectedService, user, updateUser]);
- const disabled = (Object.keys(selectedService).length < 1) ? true : false
+ const disabled = Object.keys(selectedService).length < 1;
return (
<div className="flex flex-col gap-4">
<DataTable
columns={columns}
data={data.entity}
- filteredColumn='name'
+ filteredColumn="name"
selectedRow={selectedService}
setSelectedRow={setSelectedService}
/>
- <Button onClick={handleApply} disabled={disabled}>{t('apply')}</Button>
+ <Button onClick={handleApply} disabled={disabled}>
+ {t("apply")}
+ </Button>
</div>
- )
-}
+ );
+};