260224:1606 20260224:1600 V1.8.0
All checks were successful
Build and Deploy / deploy (push) Successful in 6m25s
All checks were successful
Build and Deploy / deploy (push) Successful in 6m25s
This commit is contained in:
@@ -42,24 +42,24 @@ interface FieldConfig {
|
||||
label: string;
|
||||
type: "text" | "textarea" | "checkbox" | "select";
|
||||
required?: boolean;
|
||||
options?: { label: string; value: any }[];
|
||||
options?: { label: string; value: string | number | boolean }[];
|
||||
}
|
||||
|
||||
interface GenericCrudTableProps {
|
||||
interface GenericCrudTableProps<TEntity extends { id: number }> {
|
||||
entityName: string;
|
||||
queryKey: string[];
|
||||
fetchFn: () => Promise<any>;
|
||||
createFn: (data: any) => Promise<any>;
|
||||
updateFn: (id: number, data: any) => Promise<any>;
|
||||
deleteFn: (id: number) => Promise<any>;
|
||||
columns: ColumnDef<any>[];
|
||||
fetchFn: () => Promise<TEntity[]>;
|
||||
createFn: (data: Record<string, unknown>) => Promise<TEntity>;
|
||||
updateFn: (id: number, data: Record<string, unknown>) => Promise<TEntity>;
|
||||
deleteFn: (id: number) => Promise<unknown>;
|
||||
columns: ColumnDef<TEntity>[];
|
||||
fields: FieldConfig[];
|
||||
title?: string;
|
||||
description?: string;
|
||||
filters?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function GenericCrudTable({
|
||||
export function GenericCrudTable<TEntity extends { id: number }>({
|
||||
entityName,
|
||||
queryKey,
|
||||
fetchFn,
|
||||
@@ -71,11 +71,11 @@ export function GenericCrudTable({
|
||||
title,
|
||||
description,
|
||||
filters,
|
||||
}: GenericCrudTableProps) {
|
||||
}: GenericCrudTableProps<TEntity>) {
|
||||
const queryClient = useQueryClient();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [editingItem, setEditingItem] = useState<any>(null);
|
||||
const [formData, setFormData] = useState<any>({});
|
||||
const [editingItem, setEditingItem] = useState<TEntity | null>(null);
|
||||
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
||||
|
||||
// Delete Dialog State
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
@@ -97,7 +97,7 @@ export function GenericCrudTable({
|
||||
});
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: any }) => updateFn(id, data),
|
||||
mutationFn: ({ id, data }: { id: number; data: Record<string, unknown> }) => updateFn(id, data),
|
||||
onSuccess: () => {
|
||||
toast.success(`${entityName} updated successfully`);
|
||||
queryClient.invalidateQueries({ queryKey });
|
||||
@@ -123,7 +123,7 @@ export function GenericCrudTable({
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleEdit = (item: any) => {
|
||||
const handleEdit = (item: TEntity) => {
|
||||
setEditingItem(item);
|
||||
setFormData({ ...item });
|
||||
setIsOpen(true);
|
||||
@@ -155,8 +155,8 @@ export function GenericCrudTable({
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field: string, value: any) => {
|
||||
setFormData((prev: any) => ({ ...prev, [field]: value }));
|
||||
const handleChange = (field: string, value: unknown) => {
|
||||
setFormData((prev: Record<string, unknown>) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
// Add default Actions column if not present
|
||||
@@ -165,7 +165,7 @@ export function GenericCrudTable({
|
||||
{
|
||||
id: "actions",
|
||||
header: "Actions",
|
||||
cell: ({ row }: { row: any }) => (
|
||||
cell: ({ row }: { row: { original: TEntity } }) => (
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -239,7 +239,7 @@ export function GenericCrudTable({
|
||||
{field.type === "textarea" ? (
|
||||
<Textarea
|
||||
id={field.name}
|
||||
value={formData[field.name] || ""}
|
||||
value={(formData[field.name] as string) || ""}
|
||||
onChange={(e) => handleChange(field.name, e.target.value)}
|
||||
required={field.required}
|
||||
/>
|
||||
@@ -266,7 +266,7 @@ export function GenericCrudTable({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{field.options?.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value.toString()}>
|
||||
<SelectItem key={(opt.value as string | number)} value={opt.value.toString()}>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -276,7 +276,7 @@ export function GenericCrudTable({
|
||||
<Input
|
||||
id={field.name}
|
||||
type="text"
|
||||
value={formData[field.name] || ""}
|
||||
value={(formData[field.name] as string) || ""}
|
||||
onChange={(e) => handleChange(field.name, e.target.value)}
|
||||
required={field.required}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,14 @@ import {
|
||||
} from "@/components/ui/popover";
|
||||
import { useSearchSuggestions } from "@/hooks/use-search";
|
||||
|
||||
/** Search suggestion item returned from the API */
|
||||
interface SearchSuggestion {
|
||||
id: string | number;
|
||||
type: string;
|
||||
title: string;
|
||||
documentNumber?: string;
|
||||
}
|
||||
|
||||
function useDebounceValue<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
useEffect(() => {
|
||||
@@ -87,7 +95,7 @@ export function GlobalSearch() {
|
||||
<CommandList>
|
||||
{suggestions && suggestions.length > 0 && (
|
||||
<CommandGroup heading="Suggestions">
|
||||
{suggestions.map((item: any) => (
|
||||
{(suggestions as SearchSuggestion[]).map((item) => (
|
||||
<CommandItem
|
||||
key={`${item.type}-${item.id}`}
|
||||
onSelect={() => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { useNotifications, useMarkNotificationRead } from "@/hooks/use-notification";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { Notification } from "@/types/notification";
|
||||
|
||||
export function NotificationsDropdown() {
|
||||
const router = useRouter();
|
||||
@@ -23,7 +24,7 @@ export function NotificationsDropdown() {
|
||||
const notifications = data?.items || [];
|
||||
const unreadCount = data?.unreadCount || 0;
|
||||
|
||||
const handleNotificationClick = (notification: any) => {
|
||||
const handleNotificationClick = (notification: Notification) => {
|
||||
if (!notification.isRead) {
|
||||
markAsRead.mutate(notification.notificationId);
|
||||
}
|
||||
@@ -62,7 +63,7 @@ export function NotificationsDropdown() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{notifications.slice(0, 5).map((notification: any) => (
|
||||
{notifications.slice(0, 5).map((notification: Notification) => (
|
||||
<DropdownMenuItem
|
||||
key={notification.notificationId}
|
||||
className={`flex flex-col items-start p-3 cursor-pointer ${
|
||||
|
||||
Reference in New Issue
Block a user