Enhance NewCustomerModal with callback support and toast notifications
- Add `onCustomerCreated` callback to refresh customer list after creation. - Integrate `showInfoToast` and `showSuccessToast` for validation and creation feedback. - Prevent closing modal on backdrop click; add explicit cancel button. - Refactor `addCustomer` to use `callApi` and centralized routes. - Simplify customer fetching logic in `CustomersPage` with reusable function.
This commit is contained in:
@@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|||||||
@@ -35,28 +35,28 @@ export default function CustomersPage() {
|
|||||||
const pageSize = 15;
|
const pageSize = 15;
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
useEffect(() => {
|
const loadCustomers = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetch('/api/customers')
|
try {
|
||||||
.then(async (response) => {
|
const response = await fetch('/api/customers');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
showError("Failed to fetch customers data")
|
showError("Failed to fetch customers data");
|
||||||
throw new Error(`Failed to fetch customers: ${response.statusText}`);
|
throw new Error(`Failed to fetch customers: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
const data = await response.json();
|
||||||
})
|
showInfoToast("Customers data loaded");
|
||||||
.then((data) => {
|
|
||||||
showInfoToast("Customers data loaded")
|
|
||||||
setCustomers(data);
|
setCustomers(data);
|
||||||
})
|
} catch (error) {
|
||||||
.catch((error) => {
|
showError("Failed to fetch customers data (1)");
|
||||||
showError("Failed to fetch customers data (1)")
|
|
||||||
handleError(error);
|
handleError(error);
|
||||||
setCustomers([]);
|
setCustomers([]);
|
||||||
})
|
} finally {
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadCustomers();
|
||||||
}, [handleError]);
|
}, [handleError]);
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
@@ -114,7 +114,7 @@ export default function CustomersPage() {
|
|||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<Input placeholder="Suche" value={search} onChange={(e) => setSearch(e.target.value)}/>
|
<Input placeholder="Suche" value={search} onChange={(e) => setSearch(e.target.value)}/>
|
||||||
<NewCustomerModal/>
|
<NewCustomerModal onCustomerCreated={loadCustomers}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{customers.length === 0 && loading ? (
|
{customers.length === 0 && loading ? (
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ import {CreateCustomerDto, NoteDto, PhoneNumberDto} from "@/services/customers/d
|
|||||||
import {addCustomer} from "@/services/customers/usecases/addCustomer";
|
import {addCustomer} from "@/services/customers/usecases/addCustomer";
|
||||||
import {validateCustomer} from "@/services/customers/usecases/validateCustomer";
|
import {validateCustomer} from "@/services/customers/usecases/validateCustomer";
|
||||||
import {useErrorHandler} from "@/components/error-boundary";
|
import {useErrorHandler} from "@/components/error-boundary";
|
||||||
|
import {showInfoToast, showSuccessToast} from "@/lib/ui/showToast";
|
||||||
|
|
||||||
export function NewCustomerModal() {
|
interface NewCustomerModalProps {
|
||||||
|
onCustomerCreated?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NewCustomerModal({ onCustomerCreated }: NewCustomerModalProps) {
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@@ -53,6 +58,7 @@ export function NewCustomerModal() {
|
|||||||
try {
|
try {
|
||||||
const result = await validateCustomer({email, companyName, street, zip, city});
|
const result = await validateCustomer({email, companyName, street, zip, city});
|
||||||
setMatches(result);
|
setMatches(result);
|
||||||
|
showInfoToast("Datenvalidierung abgeschlossen");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
}
|
}
|
||||||
@@ -63,7 +69,11 @@ export function NewCustomerModal() {
|
|||||||
try {
|
try {
|
||||||
const payload: CreateCustomerDto = {email, name, companyName, street, zip, city, phoneNumbers, notes};
|
const payload: CreateCustomerDto = {email, name, companyName, street, zip, city, phoneNumbers, notes};
|
||||||
await addCustomer(payload);
|
await addCustomer(payload);
|
||||||
location.reload();
|
showSuccessToast("Kunde erfolgreich erstellt");
|
||||||
|
setOpen(false);
|
||||||
|
if (onCustomerCreated) {
|
||||||
|
onCustomerCreated();
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err);
|
handleError(err);
|
||||||
}
|
}
|
||||||
@@ -215,14 +225,16 @@ export function NewCustomerModal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={() => {
|
||||||
|
// Prevent closing on backdrop click - modal can only be closed explicitly
|
||||||
|
}}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button onClick={() => {
|
<Button onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setStep(1);
|
setStep(1);
|
||||||
}}>Neue Kunde</Button>
|
}}>Neue Kunde</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="w-full max-w-5xl max-h-[95vh] overflow-y-auto">
|
<DialogContent className="w-full max-w-5xl max-h-[95vh] overflow-y-auto" showCloseButton={false}>
|
||||||
<DialogHeader><DialogTitle>Neuen Kunden anlegen</DialogTitle></DialogHeader>
|
<DialogHeader><DialogTitle>Neuen Kunden anlegen</DialogTitle></DialogHeader>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="text-xs font-semibold mb-2">Schritt {step} von 2</div>
|
<div className="text-xs font-semibold mb-2">Schritt {step} von 2</div>
|
||||||
@@ -233,9 +245,14 @@ export function NewCustomerModal() {
|
|||||||
{matches.length > 0 && renderDuplicationCard()}
|
{matches.length > 0 && renderDuplicationCard()}
|
||||||
|
|
||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||||
|
Abbrechen
|
||||||
|
</Button>
|
||||||
<Button variant="secondary" disabled={step === 1} onClick={() => setStep(step - 1)}>
|
<Button variant="secondary" disabled={step === 1} onClick={() => setStep(step - 1)}>
|
||||||
Zurück
|
Zurück
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
{step === 1 ? (
|
{step === 1 ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setStep(2)}
|
onClick={() => setStep(2)}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import {CreateCustomerDto} from "@/services/customers/dtos/createCustomer.dto";
|
import {CreateCustomerDto} from "@/services/customers/dtos/createCustomer.dto";
|
||||||
|
import {customerRoutes} from "@/app/api/customers/customerRoutes";
|
||||||
|
import {callApi} from "@/lib/api/callApi";
|
||||||
|
import {UUID} from "node:crypto";
|
||||||
|
|
||||||
export async function addCustomer(params: CreateCustomerDto): Promise<void> {
|
export async function addCustomer(params: CreateCustomerDto): Promise<void> {
|
||||||
const {email, name, companyName, street, zip, city, phoneNumbers, notes} = params;
|
const {email, name, companyName, street, zip, city, phoneNumbers, notes} = params;
|
||||||
@@ -16,15 +19,6 @@ export async function addCustomer(params: CreateCustomerDto): Promise<void> {
|
|||||||
notes: notes.map(({text}) => ({text})),
|
notes: notes.map(({text}) => ({text})),
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch('/api/customers', {
|
const response = await callApi<UUID>(customerRoutes.create, "POST", payload);
|
||||||
method: 'POST',
|
console.log(response);
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to create customer: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user