Hotfix Rule: Force Fresh Data After CRUD Mutation

Task-h not implements
This commit is contained in:
phaichayon
2026-06-16 11:59:41 +07:00
parent 357414c247
commit 56683ee7b9
16 changed files with 763 additions and 186 deletions

View File

@@ -11,26 +11,57 @@ import {
import { customerKeys } from './queries';
import type { CustomerContactMutationPayload, CustomerMutationPayload } from './types';
async function invalidateCustomerLists() {
await getQueryClient().invalidateQueries({ queryKey: customerKeys.lists() });
}
async function invalidateCustomerDetail(id: string) {
await getQueryClient().invalidateQueries({ queryKey: customerKeys.detail(id) });
}
async function invalidateCustomerContacts(id: string) {
await getQueryClient().invalidateQueries({ queryKey: customerKeys.contacts(id) });
}
export async function invalidateCustomerMutationQueries(id: string) {
await Promise.all([invalidateCustomerLists(), invalidateCustomerDetail(id)]);
}
export async function invalidateCustomerContactMutationQueries(customerId: string) {
await Promise.all([
invalidateCustomerLists(),
invalidateCustomerDetail(customerId),
invalidateCustomerContacts(customerId)
]);
}
export const createCustomerMutation = mutationOptions({
mutationFn: (data: CustomerMutationPayload) => createCustomer(data),
onSuccess: () => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.all });
onSettled: async (_data, error) => {
if (!error) {
await invalidateCustomerLists();
}
}
});
export const updateCustomerMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: CustomerMutationPayload }) =>
updateCustomer(id, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.all });
getQueryClient().invalidateQueries({ queryKey: customerKeys.detail(variables.id) });
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateCustomerMutationQueries(variables.id);
}
}
});
export const deleteCustomerMutation = mutationOptions({
mutationFn: (id: string) => deleteCustomer(id),
onSuccess: () => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.all });
onSettled: async (_data, error, id) => {
if (!error) {
getQueryClient().removeQueries({ queryKey: customerKeys.detail(id) });
getQueryClient().removeQueries({ queryKey: customerKeys.contacts(id) });
await invalidateCustomerLists();
}
}
});
@@ -42,9 +73,10 @@ export const createCustomerContactMutation = mutationOptions({
customerId: string;
values: CustomerContactMutationPayload;
}) => createCustomerContact(customerId, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.contacts(variables.customerId) });
getQueryClient().invalidateQueries({ queryKey: customerKeys.detail(variables.customerId) });
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateCustomerContactMutationQueries(variables.customerId);
}
}
});
@@ -58,17 +90,19 @@ export const updateCustomerContactMutation = mutationOptions({
contactId: string;
values: CustomerContactMutationPayload;
}) => updateCustomerContact(customerId, contactId, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.contacts(variables.customerId) });
getQueryClient().invalidateQueries({ queryKey: customerKeys.detail(variables.customerId) });
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateCustomerContactMutationQueries(variables.customerId);
}
}
});
export const deleteCustomerContactMutation = mutationOptions({
mutationFn: ({ customerId, contactId }: { customerId: string; contactId: string }) =>
deleteCustomerContact(customerId, contactId),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: customerKeys.contacts(variables.customerId) });
getQueryClient().invalidateQueries({ queryKey: customerKeys.detail(variables.customerId) });
onSettled: async (_data, error, variables) => {
if (!error) {
await invalidateCustomerContactMutationQueries(variables.customerId);
}
}
});

View File

@@ -4,9 +4,12 @@ import type { CustomerFilters } from './types';
export const customerKeys = {
all: ['crm-customers'] as const,
list: (filters: CustomerFilters) => [...customerKeys.all, 'list', filters] as const,
detail: (id: string) => [...customerKeys.all, 'detail', id] as const,
contacts: (id: string) => [...customerKeys.all, 'contacts', id] as const
lists: () => [...customerKeys.all, 'list'] as const,
list: (filters: CustomerFilters) => [...customerKeys.lists(), filters] as const,
details: () => [...customerKeys.all, 'detail'] as const,
detail: (id: string) => [...customerKeys.details(), id] as const,
contactsRoot: () => [...customerKeys.all, 'contacts'] as const,
contacts: (id: string) => [...customerKeys.contactsRoot(), id] as const
};
export const customersQueryOptions = (filters: CustomerFilters) =>

View File

@@ -9,6 +9,7 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { formatDateTime } from '@/lib/format';
import { customerByIdOptions } from '../api/queries';
import type { CustomerReferenceData } from '../api/types';
import { CustomerFormSheet } from './customer-form-sheet';
@@ -30,6 +31,7 @@ export function CustomerDetail({
customerId,
referenceData,
canUpdate,
canReadContacts,
canManageContacts,
relatedEnquiries,
relatedQuotations
@@ -37,6 +39,7 @@ export function CustomerDetail({
customerId: string;
referenceData: CustomerReferenceData;
canUpdate: boolean;
canReadContacts: boolean;
canManageContacts: {
create: boolean;
update: boolean;
@@ -113,7 +116,7 @@ export function CustomerDetail({
<Tabs defaultValue='overview' className='gap-4'>
<TabsList>
<TabsTrigger value='overview'>Overview</TabsTrigger>
<TabsTrigger value='contacts'>Contacts</TabsTrigger>
{canReadContacts ? <TabsTrigger value='contacts'>Contacts</TabsTrigger> : null}
<TabsTrigger value='activity'>Activity</TabsTrigger>
<TabsTrigger value='related'>Related Documents</TabsTrigger>
</TabsList>
@@ -166,14 +169,16 @@ export function CustomerDetail({
</CardContent>
</Card>
</TabsContent>
<TabsContent value='contacts'>
<CustomerContactsTab
customerId={customerId}
canCreate={canManageContacts.create}
canUpdate={canManageContacts.update}
canDelete={canManageContacts.delete}
/>
</TabsContent>
{canReadContacts ? (
<TabsContent value='contacts'>
<CustomerContactsTab
customerId={customerId}
canCreate={canManageContacts.create}
canUpdate={canManageContacts.update}
canDelete={canManageContacts.delete}
/>
</TabsContent>
) : null}
<TabsContent value='activity'>
<Card>
<CardHeader>
@@ -202,7 +207,7 @@ export function CustomerDetail({
</span>
</div>
<div className='text-muted-foreground text-xs'>
{new Date(item.createdAt).toLocaleString()}
{formatDateTime(item.createdAt)}
</div>
</div>
</div>
@@ -281,8 +286,8 @@ export function CustomerDetail({
<CardContent className='space-y-4'>
<FieldItem label='Created By' value={customer.createdBy} />
<FieldItem label='Updated By' value={customer.updatedBy} />
<FieldItem label='Created At' value={new Date(customer.createdAt).toLocaleString()} />
<FieldItem label='Updated At' value={new Date(customer.updatedAt).toLocaleString()} />
<FieldItem label='Created At' value={formatDateTime(customer.createdAt)} />
<FieldItem label='Updated At' value={formatDateTime(customer.updatedAt)} />
</CardContent>
</Card>
</div>