Premiers pas avec Appwrite (partie 3)

Cette série explorera Appwrite, un backend de serveur auto-hébergé, ainsi qu’une plate-forme BaaS. Nous plongerons dans une application de facturation React, présentant le flux d’authentification, le stockage de base de données et les fonctions sans serveur. La partie 3 comprendra la mise en œuvre de la fonctionnalité CRUD pour les factures.
Bienvenue dans la troisième partie de la série « Premiers pas avec Appwrite ». Dans le partie précédentenous avons implémenté une fonctionnalité qui permet aux utilisateurs de créer un nouveau compte et de se connecter. Dans cette partie, nous nous concentrerons sur les fonctionnalités qui permettront aux utilisateurs de créer, mettre à jour, supprimer et répertorier les factures.
Méthodes CRUD de l’API de facturation
Nous allons commencer par implémenter les méthodes API suivantes pour les requêtes de facture CRUD :
- listeFactures – récupère toutes les factures de la collection de factures.
- obtenir une facture – récupère une facture spécifique en utilisant son identifiant de document.
- créer une facture – crée une nouvelle facture avec des autorisations qui permettront à l’utilisateur de la lire, de la mettre à jour et de la supprimer.
- mettre à jour la facture – met à jour une facture par son identifiant de document.
- supprimerFacture – supprime une facture.
Voici le code du invoice.api.js
déposer.
src/api/invoice.api.js
import { ID, Permission, Role } from "appwrite";
import { databases, databaseId } from "./appwrite.api";
const invoiceCollectionId = import.meta.env
.VITE_APPWRITE_COLLECTION_ID_INVOICES;
export const listInvoices = () => {
return databases.listDocuments(databaseId, invoiceCollectionId);
};
export const getInvoice = documentId => {
return databases.getDocument(databaseId, invoiceCollectionId, documentId);
};
export const createInvoice = (userId, payload) => {
const ownerRole = Role.user(userId);
return databases.createDocument(
databaseId,
invoiceCollectionId,
ID.unique(),
payload,
[
Permission.read(ownerRole),
Permission.update(ownerRole),
Permission.delete(ownerRole),
]
);
};
export const updateInvoice = (documentId, payload) => {
return databases.updateDocument(
databaseId,
invoiceCollectionId,
documentId,
payload
);
};
export const deleteInvoice = documentId => {
return databases.deleteDocument(databaseId, invoiceCollectionId, documentId);
};
Pour effectuer des opérations sur l’encaissement des factures, nous avons besoin de connaître son identifiant. Nous l’avons mis dans le fichier .env dans la première partie de la série, mais si vous ne l’avez pas, rendez-vous sur le tableau de bord Appwrite puis sur la page de la base de données ou de la collecte des factures. Les deux pages affichent l’ID de la collection.
Vous avez peut-être remarqué cela dans createInvoice
méthode, nous transmettons un tableau avec des autorisations de lecture, de mise à jour et de suppression. Comme nous l’avons vu précédemment dans Partie 1Appwrite fournit deux niveaux d’autorisations :niveau de collecte et niveau du document. Les autorisations au niveau de la collection sont appliquées à chaque document de la collection et nous avons ajouté une autorisation permettant à tous les utilisateurs authentifiés de créer des factures. Cependant, nous utilisons également les autorisations au niveau des documents, car nous avons besoin d’un contrôle précis sur qui doit pouvoir lire, mettre à jour et supprimer les factures.
Dans ce scénario, seul le créateur des factures devrait pouvoir effectuer des opérations sur celles-ci. C’est pourquoi, dans le createInvoice
méthode, nous passons le tableau d’autorisations suivant comme argument :
[
Permission.read(ownerRole),
Permission.update(ownerRole),
Permission.delete(ownerRole),
]
Il est également possible de créer des autorisations pour les équipes, leurs membres spécifiques, les étiquettes et bien plus encore.
Outre les autorisations, j’aimerais attirer votre attention sur une autre chose intéressante ici. Avez-vous remarqué que nous ne fournissons aucun filtre au listInvoices
méthode? Puisque nous souhaitons récupérer les factures uniquement pour l’utilisateur connecté, nous devrons normalement spécifier un filtre, qui nécessitera généralement l’identifiant de l’utilisateur. Cependant, Appwrite, par défaut, filtre les documents de collection en fonction des autorisations, de sorte que les utilisateurs n’obtiendront que les documents qu’ils sont autorisés à lire.
Une dernière chose que nous devons faire dans les fichiers API est d’ajouter le databaseId
variable dans le appwrite.api.js
déposer.
src/api/appwrite.api.js
import { Client, Databases, Account, Storage } from "appwrite";
const client = new Client();
client
.setEndpoint("https://cloud.appwrite.io/v1")
.setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID);
export const account = new Account(client);
export const databases = new Databases(client);
export const storage = new Storage(client);
export const databaseId = import.meta.env.VITE_APPWRITE_DATABASE_ID;
Créer, mettre à jour et supprimer un formulaire de facture
Les méthodes API sont prêtes, implémentons donc un formulaire pour permettre aux utilisateurs de créer et de mettre à jour des factures. Dans le navigateur, accédez à ce qui suit /invoice/create
chemin. De la même manière que pour le formulaire d’authentification, nous aurons un composant de formulaire qui se comportera différemment selon qu’un utilisateur essaie de créer une nouvelle facture ou d’en mettre à jour une. Ceci sera déterminé par la présence de l’identifiant de facture dans l’URL.
Si vous jetez un oeil à src/main.jsx fichier, vous verrez que le Invoice
Le composant gère deux définitions d’itinéraire :/invoice/create
et /invoice/:id
.
{
path: "/invoice/create",
element: <Invoice />,
},
{
path: "/invoice/:id",
element: <Invoice />,
},
Nous allons commencer par créer un hook personnalisé qui stockera l’état du formulaire de facture.
src/views/invoice/hooks/useInvoiceForm.js
import { useState } from "react";
import { useParams } from "react-router-dom";
export const useInvoiceForm = () => {
const params = useParams();
const isEditMode = Boolean(params.id);
const [form, setForm] = useState({
invoiceId: "",
date: "",
dueDate: "",
amount: "",
description: "",
senderName: "",
senderAddress: "",
senderPostcode: "",
senderCity: "",
senderCountry: "",
senderEmail: "",
senderPhone: "",
clientName: "",
clientAddress: "",
clientPostcode: "",
clientCity: "",
clientCountry: "",
clientEmail: "",
clientPhone: "",
accountName: "",
accountSortCode: "",
accountNumber: "",
accountAddress: "",
accountIban: "",
accountPostCode: "",
accountCity: "",
accountCountry: "",
paymentReceived: false,
paymentDate: "",
});
const onFormChange = key => value => {
setForm(state => ({
...state,
[key]: value,
}));
};
return {
form,
setForm,
onFormChange,
isEditMode,
};
};
Vous vous souviendrez peut-être de la première partie de cette série que nous avons beaucoup de champs à traiter, car les factures peuvent contenir pas mal d’informations. Par conséquent, nous mettons la logique dans des hooks personnalisés pour conserver le Invoice
composant succinct. Le useInvoiceForm
hook gère cet état de forme et fournit des méthodes pour le modifier. Maintenant, nous pouvons ajouter un hook personnalisé pour gérer la soumission du formulaire.
src/views/invoice/hooks/useSubmitInvoice.js
import { useState } from "react";
import { createInvoice, updateInvoice } from "../../../api/invoice.api";
import toast from "react-hot-toast";
import { useUserContext } from "../../../context/user.context";
import { useNavigate } from "react-router-dom";
export const useSubmitInvoice = ({ form, isEditMode }) => {
const { user } = useUserContext();
const navigate = useNavigate();
const [submitInvoiceStatus, setSubmitInvoiceStatus] = useState("IDLE");
const onSubmitInvoice = async event => {
event.preventDefault();
try {
if (submitInvoiceStatus === "PENDING") {
return;
}
setSubmitInvoiceStatus("PENDING");
const payload = {};
for (const [key, value] of Object.entries(form)) {
if (value !== "") {
payload[key] = value;
}
}
if (isEditMode) {
await updateInvoice(form.$id, payload);
toast.success("Invoice updated");
} else {
await createInvoice(user.$id, payload);
toast.success("Invoice created");
}
setSubmitInvoiceStatus("SUCCESS");
navigate("https://www.telerik.com/");
} catch (error) {
console.error(error);
setSubmitInvoiceStatus("ERROR");
}
};
return {
submitInvoiceStatus,
onSubmitInvoice,
};
};
Le submitInvoiceStatus
state sera utilisé pour afficher une icône ou une erreur lorsque la soumission du formulaire est en attente ou échoue. Cet État est contrôlé principalement par le onSubmitInvoice
gestionnaire. Le onSubmitInvoice
utilise le isEditMode
variable pour déterminer si elle doit créer ou mettre à jour la facture. Une fois la soumission réussie, une notification toast s’affiche et l’utilisateur est redirigé vers la page des factures. S’il y a un problème, le submitInvoiceStatus
est réglé sur "ERROR"
.
La logique permettant de gérer la soumission du formulaire de facture est prête, mais ce n’est pas encore suffisant. Si un utilisateur souhaite mettre à jour une facture, nous devons d’abord récupérer les détails de la facture. Nous pourrions en mettre la logique dans le useInvoiceForm
hook, mais implémentons un nouveau hook appelé useFetchInvoice
au lieu de cela, garder le code simple.
src/views/invoice/hooks/useFetchInvoice.js
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { getInvoice } from "../../../api/invoice.api";
import { formatDate } from "../../../helpers/formatDate";
export const useFetchInvoice = ({ id, onSetInvoice }) => {
const [fetchInvoiceStatus, setFetchInvoiceStatus] = useState(
id ? "IDLE" : "SUCCESS"
);
const initFetchInvoice = async invoiceUid => {
try {
if (fetchInvoiceStatus === "PENDING") {
return;
}
setFetchInvoiceStatus("PENDING");
const invoice = await getInvoice(invoiceUid);
onSetInvoice(currentForm => {
const newForm = {
$id: invoice.$id,
};
for (const key of Object.keys(currentForm)) {
const value = invoice[key];
if (["date", "dueDate", "paymentDate"].includes(key) && value) {
if (!value) {
newForm[key] = "";
} else {
const [month, day, year] = formatDate(new Date(value)).split("https://www.telerik.com/");
newForm[key] = `${year}-${month}-${day}`;
}
} else {
newForm[key] = value === null ? "" : value;
}
}
return newForm;
});
setFetchInvoiceStatus("SUCCESS");
} catch (error) {
console.error(error);
toast.error("There was a problem while fetching the invoice.");
setFetchInvoiceStatus("ERROR");
}
};
useEffect(() => {
if (!id) {
return;
}
initFetchInvoice(id);
}, [id]);
return {
fetchInvoiceStatus,
initFetchInvoice,
};
};
useFetchInvoice
utilise useEffect
et useParams
crochets pour exécuter le initFetchInvoice
fonction. Cette fonction récupère la facture à l’aide de son identifiant et formate les données de la facture lors de la mise à jour de l’état du formulaire. L’état du formulaire est mis à jour à l’aide du onSetInvoice
qui est transmis au useFetchInvoice
crochet. Nous devons également créer le formatDate
assistant. Il se chargera d’afficher les dates dans un format plus lisible.
src/helpers/formatDate.js
export const formatDate = (date, options) => {
if (!date) return "";
return new Intl.DateTimeFormat("en-US", {
day: "2-digit",
month: "2-digit",
year: "numeric",
...options,
}).format(date);
};
Dans cet exemple, nous passons le en-US
lieu vers le Intl.DateTimeFormat
méthode, mais si vous êtes prêt à relever un défi, vous pouvez essayer de fournir des paramètres régionaux personnalisés en fonction de l’emplacement de l’utilisateur.
Maintenant, ajoutons un hook pour gérer la suppression des factures.
src/views/invoice/hooks/useDeleteInvoice.js
import { useState } from "react";
import toast from "react-hot-toast";
import { useNavigate } from "react-router-dom";
import { deleteInvoice } from "../../../api/invoice.api";
export const useDeleteInvoice = ({ invoiceId }) => {
const navigate = useNavigate();
const [deleteInvoiceStatus, setDeleteInvoiceStatus] = useState("IDLE");
const initDeletePrompt = async () => {
if (deleteInvoiceStatus === "PENDING") {
return;
}
const result = window.confirm(
"Are you sure you want to delete this invoice?"
);
if (!result) {
return;
}
try {
setDeleteInvoiceStatus("PENDING");
await deleteInvoice(invoiceId);
setDeleteInvoiceStatus("SUCCESS");
toast.success("Invoice deleted");
navigate("https://www.telerik.com/");
} catch (error) {
console.error(error);
toast.error("Could not delete the invoice");
setDeleteInvoiceStatus("ERROR");
}
};
return {
deleteInvoiceStatus,
initDeletePrompt,
};
};
Encore une fois, nous avons un État qui est responsable du stockage du statut de la suppression et du initDeletePrompt
méthode. Avant de déclencher une action de suppression, il est judicieux de demander à un utilisateur de confirmer s’il souhaite réellement procéder à la suppression. Après tout, qui n’a jamais cliqué sur un bouton Supprimer par accident ? Dans ce cas, on utilise simplement le window.confirm
méthode car elle est suffisante pour ce tutoriel.
Nous disposons désormais de hooks qui gèrent la récupération des factures, la soumission et la suppression des formulaires. Il est temps de créer enfin le formulaire de facture. Direction le Facture.jsx déposer.
src/views/invoice/Invoice.jsx
import { Link, useParams } from "react-router-dom";
import BankDetails from "./components/BankDetails";
import ClientDetails from "./components/ClientDetails";
import InvoiceDetails from "./components/InvoiceDetails";
import SenderDetails from "./components/SenderDetails";
import { useDeleteInvoice } from "./hooks/useDeleteInvoice";
import { useFetchInvoice } from "./hooks/useFetchInvoice";
import { useInvoiceForm } from "./hooks/useInvoiceForm";
import { useSubmitInvoice } from "./hooks/useSubmitInvoice";
const config = {
create: {
submitButtonText: "Create",
},
update: {
submitButtonText: "Update",
},
};
const Invoice = () => {
const params = useParams();
const { isEditMode, form, setForm, onFormChange } = useInvoiceForm();
const { fetchInvoiceStatus, initFetchInvoice } = useFetchInvoice({
id: params.id,
onSetInvoice: setForm,
});
const { submitInvoiceStatus, onSubmitInvoice } = useSubmitInvoice({
form,
isEditMode,
});
const { deleteInvoiceStatus, initDeletePrompt } = useDeleteInvoice({
invoiceId: form.$id,
});
const { submitButtonText } = isEditMode ? config.update : config.create;
return (
<div className="flex items-center justify-center w-full min-h-screen bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400">
<div className="min-h-screen px-8 pb-16 bg-white md:w-3/4 md:ml-auto md:pr-0 md:pl-16 md:pb-24">
<div className="flex items-center justify-between mr-8">
<h1 className="my-8 text-2xl font-semibold text-indigo-900">
Invoice
</h1>
<Link
className="text-sm transition-all duration-150 text-indigo-900/50 hover:text-indigo-900"
to="/"
>
Back To Invoices
</Link>
</div>
{fetchInvoiceStatus === "PENDING" ? (
<div>Fetching invoice data...</div>
) : null}
{fetchInvoiceStatus === "ERROR" ? (
<div>
<button
className="px-4 py-2 bg-indigo-600 rounded-md text-indigo-50"
onClick={() => initFetchInvoice(params.id)}
>
Try Again
</button>
</div>
) : null}
{fetchInvoiceStatus === "SUCCESS" ? (
<form
className="flex flex-col max-w-5xl gap-8"
onSubmit={onSubmitInvoice}
>
<div className="flex flex-col gap-8 md:gap-12">
<InvoiceDetails form={form} onFormChange={onFormChange} />
<SenderDetails form={form} onFormChange={onFormChange} />
<ClientDetails form={form} onFormChange={onFormChange} />
<BankDetails form={form} onFormChange={onFormChange} />
</div>
<div className="flex justify-between">
<button
type="button"
className="min-w-[6rem] px-4 py-3 mr-4 font-semibold text-indigo-800 transition-colors duration-150 bg-indigo-200/25 rounded-md hover:bg-rose-800 hover:text-rose-100"
onClick={initDeletePrompt}
>
{deleteInvoiceStatus === "PENDING" ? "Deleting..." : "Delete"}
</button>
<button
type="submit"
className="min-w-[6rem] px-4 py-3 mr-8 font-semibold text-indigo-100 transition-colors duration-150 bg-indigo-600 rounded-md hover:bg-indigo-800"
>
{submitInvoiceStatus === "PENDING"
? "Submitting..."
: submitButtonText}
</button>
</div>
</form>
) : null}
</div>
</div>
);
};
export default Invoice;
Dans le Invoice
composant, nous utilisons les hooks personnalisés que nous venons de créer. Le texte du bouton Soumettre est calculé à l’aide de la isEditMode
et submitInvoiceStatus
valeurs. Si la soumission de la facture est en cours, il affiche Submitting...
texte. Sinon, ce sera soit Create
ou Update
.
Pour rendre les choses plus faciles à gérer, le formulaire de facture est divisé en quatre éléments :InvoiceDetails
, SenderDetails
, ClientDetails
et BankDetails
. Ces composants reçoivent le form
l’état et onFormChange
méthode. Créons-les maintenant.
src/views/invoice/components/InvoiceDetails.jsx
import Input from "../../../components/form/Input";
const InvoiceDetails = props => {
const { form, onFormChange } = props;
return (
<div>
<div>
<h2 className="mb-4 text-sm font-semibold text-indigo-600/75">
Invoice Details
</h2>
</div>
<div className="flex flex-wrap gap-x-8 gap-y-4 md:[&>*]:basis-[calc(50%-2rem)]">
<Input
label="Invoice ID"
required
value={form.invoiceId}
onChange={onFormChange("invoiceId")}
/>
<Input
label="Invoice Date"
type="date"
required
value={form.date}
onChange={onFormChange("date")}
/>
<Input
label="Invoice Due Date"
type="date"
required
value={form.dueDate}
onChange={onFormChange("dueDate")}
/>
<Input
label="Invoice Amount"
value={form.amount}
required
onChange={onFormChange("amount")}
/>
<Input
rootProps={{
style: {
flexBasis: "calc(100% - 2rem)",
},
}}
label="Description"
required
value={form.description}
onChange={onFormChange("description")}
/>
<Input
label="Payment Date"
type="date"
value={form.paymentDate}
onChange={onFormChange("paymentDate")}
/>
<div className="flex flex-col w-full gap-1">
<label className="text-sm text-indigo-950/75">Payment Received</label>
<div className="flex gap-4">
<button
type="button"
className={`flex-grow px-4 py-2 rounded-md ${
form.paymentReceived
? "bg-indigo-100"
: "bg-indigo-600 text-indigo-100"
}`}
onClick={() => {
onFormChange("paymentReceived")(false);
}}
>
No
</button>
<button
type="button"
className={`flex-grow px-4 py-2 rounded-md ${
form.paymentReceived
? "bg-indigo-600 text-indigo-100"
: "bg-indigo-100"
}`}
onClick={() => {
onFormChange("paymentReceived")(true);
}}
>
Yes
</button>
</div>
</div>
</div>
</div>
);
};
export default InvoiceDetails;
src/views/invoice/components/SenderDetails.jsx
import Input from "../../../components/form/Input";
const SenderDetails = props => {
const { form, onFormChange } = props;
return (
<div>
<h2 className="mb-4 text-sm font-semibold text-indigo-600/75">
Sender Details
</h2>
<div className="flex flex-wrap gap-x-8 gap-y-4 md:[&>*]:basis-[calc(50%-2rem)]">
<Input
label="Name"
value={form.senderName}
onChange={onFormChange("senderName")}
/>
<Input
label="Address"
value={form.senderAddress}
onChange={onFormChange("senderAddress")}
/>
<Input
label="Postcode"
value={form.senderPostcode}
onChange={onFormChange("senderPostcode")}
/>
<Input
label="City"
value={form.senderCity}
onChange={onFormChange("senderCity")}
/>
<Input
label="Country"
value={form.senderCountry}
onChange={onFormChange("senderCountry")}
/>
<Input
label="Email"
value={form.senderEmail}
onChange={onFormChange("senderEmail")}
/>
<Input
label="Phone"
value={form.senderPhone}
onChange={onFormChange("senderPhone")}
/>
</div>
</div>
);
};
export default SenderDetails;
src/views/invoice/components/ClientDetails.jsx
import Input from "../../../components/form/Input";
const ClientDetails = props => {
const { form, onFormChange } = props;
return (
<div>
<h2 className="mb-4 text-sm font-semibold text-indigo-600/75">
Client Details
</h2>
<div className="flex flex-wrap gap-x-8 gap-y-4 md:[&>*]:basis-[calc(50%-2rem)]">
<Input
label="Client Name"
value={form.clientName}
onChange={onFormChange("clientName")}
/>
<Input
label="Client Address"
value={form.clientAddress}
onChange={onFormChange("clientAddress")}
/>
<Input
label="Client Postcode"
value={form.clientPostcode}
onChange={onFormChange("clientPostcode")}
/>
<Input
label="Client City"
value={form.clientCity}
onChange={onFormChange("clientCity")}
/>
<Input
label="Client Country"
value={form.clientCountry}
onChange={onFormChange("clientCountry")}
/>
<Input
label="Client Email"
value={form.clientEmail}
onChange={onFormChange("clientEmail")}
/>
<Input
label="Client Phone"
value={form.clientPhone}
onChange={onFormChange("clientPhone")}
/>
</div>
</div>
);
};
export default ClientDetails;
src/views/invoice/components/BankDetails.jsx
import Input from "../../../components/form/Input";
const BankDetails = props => {
const { form, onFormChange } = props;
return (
<div>
<h2 className="mb-4 text-sm font-semibold text-indigo-600/75">
Bank Details
</h2>
<div className="flex flex-wrap gap-x-8 gap-y-4 md:[&>*]:basis-[calc(50%-2rem)]">
<Input
label="Account Name"
value={form.accountName}
onChange={onFormChange("accountName")}
/>
<Input
label="Account Number"
value={form.accountNumber}
onChange={onFormChange("accountNumber")}
/>
<Input
label="Sort Code"
value={form.accountSortCode}
onChange={onFormChange("accountSortCode")}
/>
<Input
label="IBAN"
value={form.accountIban}
onChange={onFormChange("accountIban")}
/>
<Input
label="Address"
value={form.accountAddress}
onChange={onFormChange("accountAddress")}
/>
<Input
label="Postcode"
value={form.accountPostCode}
onChange={onFormChange("accountPostCode")}
/>
<Input
label="City"
value={form.accountCity}
onChange={onFormChange("accountCity")}
/>
<Input
label="Country"
value={form.accountCountry}
onChange={onFormChange("accountCountry")}
/>
</div>
</div>
);
};
export default BankDetails;
Les quatre composantes comprennent principalement Input
composants nécessaires au formulaire de facture. Pour simplifier les choses, nous n’implémentons pas la validation, car le formulaire est vraiment volumineux et la validation du formulaire n’est pas l’objectif principal de cette série. Cependant, si vous êtes prêt à relever un défi, vous pouvez essayer de l’ajouter vous-même.
Le GIF ci-dessous montre à quoi devrait ressembler le formulaire de facture maintenant.
Maintenant, vous pouvez remplir tous les champs du formulaire et cliquer sur le Create
bouton. Une nouvelle facture devrait être créée et vous devriez être redirigé vers la page des factures, qui pour le moment n’affichera pas la facture nouvellement créée. Après tout, nous devons encore créer cette fonctionnalité. Faisons-le ensuite. Si vous êtes toujours sur la page du formulaire de facture, vous pouvez cliquer sur le bouton Retour aux factures lien.
Afficher les factures
Nous allons commencer par créer un hook personnalisé appelé useFetchInvoicesList
.
src/views/invoice/hooks/useFetchInvoicesList.js
import { useEffect, useState } from "react";
import { listInvoices } from "../../../api/invoice.api";
import { formatDate } from "../../../helpers/formatDate";
export const useFetchInvoicesList = () => {
const [invoices, setInvoices] = useState([]);
const [fetchInvoicesStatus, setFetchInvoiceStatus] = useState("IDLE");
const initFetchInvoices = async () => {
try {
setFetchInvoiceStatus("PENDING");
const result = await listInvoices();
const formattedInvoices = result.documents.map(invoice => {
const { date, dueDate, ...invoiceData } = invoice;
return {
...invoiceData,
date: formatDate(new Date(date)),
dueDate: formatDate(new Date(dueDate)),
};
});
setInvoices(formattedInvoices);
setFetchInvoiceStatus("SUCCESS");
} catch (error) {
console.error(error);
setFetchInvoiceStatus("ERROR");
}
};
useEffect(() => {
initFetchInvoices();
}, []);
return {
invoices,
fetchInvoicesStatus,
};
};
Le useFetchInvoicesList
hook est très similaire aux autres hooks que nous avons créés. Il contient le invoices
état, qui stockera les données récupérées et le fetchInvoicesStatus
state, qui gère la progression de la requête API. Le initFetchInvoices
La fonction récupère les factures du serveur et formate les données avant de mettre à jour le invoices
État.
Enfin, mettons à jour le ViewInvoices
composant.
src/views/invoice/ViewInvoices.jsx
import { Link } from "react-router-dom";
import { useFetchInvoicesList } from "./hooks/useFetchInvoicesList";
const ViewInvoices = () => {
const { invoices, fetchInvoicesStatus } = useFetchInvoicesList();
return (
<div className="flex items-center justify-center w-full min-h-screen text-indigo-900 bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400">
<div className="p-4 bg-white rounded-lg lg:p-8">
<div className="flex items-center justify-between gap-4 mb-8 ">
<h1 className="text-2xl font-semibold">Invoices</h1>
<Link
to="/invoice/create"
className="px-4 py-2 transition-colors duration-150 bg-indigo-50 hover:bg-indigo-600 hover:text-indigo-100"
>
Create Invoice
</Link>
</div>
{fetchInvoicesStatus === "SUCCESS" ? (
invoices.length ? (
<div>
<div className="items-start hidden lg:flex gap-x-8 lg:gap-x-16">
<span className="w-16 font-semibold text-indigo-600">ID</span>
<span className="w-32 font-semibold text-indigo-600">
Client
</span>
<span className="w-16 font-semibold text-indigo-600">
Amount
</span>
<span className="w-24 font-semibold text-indigo-600">Date</span>
<span className="w-24 font-semibold text-indigo-600">
Due Date
</span>
<span className="font-semibold text-indigo-600 w-36">
Payment Received
</span>
</div>
<ul className="mt-2">
{invoices.map(invoice => {
const {
$id,
invoiceId,
amount,
clientName,
date,
dueDate,
paymentReceived,
} = invoice;
return (
<li
key={$id}
className="px-4 py-2 lg:p-0 max-lg:my-4 max-lg:bg-indigo-50/50"
>
<Link
to={`/invoice/${$id}`}
className="p-2 -mx-2 rounded-md grid grid-cols-2 gap-y-4 lg:gap-y-0 lg:flex lg:flex-nowrap gap-x-8 lg:gap-x-16 lg:hover:bg-indigo-50 min-w-[15rem] sm:min-w-[20rem]"
>
<div className="flex flex-col lg:w-16">
<span className="text-sm text-indigo-600 lg:hidden">
ID
</span>
<span>{invoiceId}</span>
</div>
<div className="flex flex-col lg:w-32">
<span className="text-sm text-indigo-600 lg:hidden">
Client
</span>
<span>{clientName}</span>
</div>
<div className="flex flex-col lg:w-16">
<span className="text-sm text-indigo-600 lg:hidden">
Amount
</span>
<span>{amount}</span>
</div>
<div className="flex flex-col lg:w-24">
<span className="text-sm text-indigo-600 lg:hidden">
Date
</span>
<span>{date}</span>
</div>
<div className="flex flex-col lg:w-24">
<span className="text-sm text-indigo-600 lg:hidden">
Due Date
</span>
<span>{dueDate}</span>
</div>
<div className="flex flex-col lg:w-36">
<span className="text-sm text-indigo-600 lg:hidden">
Payment Received
</span>
<span>{paymentReceived ? "Yes" : "No"}</span>
</div>
</Link>
</li>
);
})}
</ul>
</div>
) : (
<Link
to="/invoice/create"
className="font-semibold text-indigo-600"
>
You have no invoices. Let's create one!
</Link>
)
) : (
<p>Loading invoices...</p>
)}
</div>
</div>
);
};
export default ViewInvoices;
L’image ci-dessous montre à quoi ressemble la page d’affichage des factures.
Cliquer sur un élément de facture redirigera un utilisateur vers le formulaire de mise à jour de la facture. Outre la liste des factures, nous avons également le Créer une facture bouton, qui redirigera les utilisateurs vers le formulaire de création de facture. Vous pouvez maintenant tester la fonctionnalité de mise à jour et de suppression, car nous n’avons pas encore eu l’occasion de le faire.
Conclusion
Dans cette partie, nous avons expliqué comment effectuer des opérations CRUD avec Appwrite. Nous avons créé une page avec le formulaire de facture qui permet aux utilisateurs de créer, mettre à jour et supprimer des factures. Nous avons également ajouté le Afficher les factures page, afin que les utilisateurs puissent consulter leurs propres factures. Dans la partie suivante, nous examinerons les fonctions et le stockage Appwrite et les combinerons pour créer des fonctionnalités qui créeront automatiquement des fichiers PDF.
Source link