Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled

Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:52:51 +01:00
commit 67d7e6e3d5
572 changed files with 86496 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
'use client';
import { useState } from 'react';
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
import { apiFetch } from '@/lib/api/client';
import type { SavedView } from '@/lib/db/schema/system';
export function useSavedViews(entityType: string) {
const queryClient = useQueryClient();
const [activeViewId, setActiveViewId] = useState<string | null>(null);
const queryKey = ['savedViews', entityType];
const { data: views = [] } = useQuery<SavedView[]>({
queryKey,
queryFn: () =>
apiFetch<{ data: SavedView[] }>(
`/api/v1/saved-views?entityType=${entityType}`,
).then((r) => r.data),
});
const saveMutation = useMutation({
mutationFn: (params: {
name: string;
filters: Record<string, unknown>;
sortConfig?: unknown;
}) =>
apiFetch<{ data: SavedView }>('/api/v1/saved-views', {
method: 'POST',
body: { ...params, entityType },
}),
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
});
const deleteMutation = useMutation({
mutationFn: (viewId: string) =>
apiFetch(`/api/v1/saved-views/${viewId}`, { method: 'DELETE' }),
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
});
async function saveCurrentView(
name: string,
filters: Record<string, unknown>,
sortConfig?: unknown,
) {
await saveMutation.mutateAsync({ name, filters, sortConfig });
}
function deleteView(viewId: string) {
if (activeViewId === viewId) setActiveViewId(null);
deleteMutation.mutate(viewId);
}
function applyView(viewId: string) {
setActiveViewId(viewId);
}
return {
views,
activeViewId,
saveCurrentView,
deleteView,
applyView,
};
}