fix: adds example CSV and other fixes on the contacts page (#4493)

Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Anshuman Pandey
2024-12-18 17:32:21 +05:30
committed by GitHub
parent 3b90f085b1
commit 7b11ef9b40
10 changed files with 99 additions and 40 deletions

View File

@@ -24,7 +24,7 @@ interface ContactDataViewProps {
itemsPerPage: number;
isReadOnly: boolean;
hasMore: boolean;
refreshContacts: () => void;
refreshContacts: () => Promise<void>;
}
export const ContactDataView = ({

View File

@@ -42,7 +42,7 @@ interface ContactsTableProps {
searchValue: string;
setSearchValue: (value: string) => void;
isReadOnly: boolean;
refreshContacts: () => void;
refreshContacts: () => Promise<void>;
}
export const ContactsTable = ({

View File

@@ -210,6 +210,29 @@ export const UploadContactsCSVButton = ({
}
}, [error]);
// Function to download an example CSV
const handleDownloadExampleCSV = () => {
const exampleData = [
{ email: "user1@example.com", userId: "1001", firstName: "John", lastName: "Doe" },
{ email: "user2@example.com", userId: "1002", firstName: "Jane", lastName: "Smith" },
{ email: "user3@example.com", userId: "1003", firstName: "Mark", lastName: "Jones" },
{ email: "user4@example.com", userId: "1004", firstName: "Emily", lastName: "Brown" },
{ email: "user5@example.com", userId: "1005", firstName: "David", lastName: "Wilson" },
];
const headers = Object.keys(exampleData[0]);
const csvRows = [headers.join(","), ...exampleData.map((row) => headers.map((h) => row[h]).join(","))];
const csvContent = "data:text/csv;charset=utf-8," + csvRows.join("\n");
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "example.csv");
document.body.appendChild(link); // Required for Firefox
link.click();
document.body.removeChild(link);
};
return (
<>
<Button size="sm" onClick={() => setOpen(true)}>
@@ -222,7 +245,7 @@ export const UploadContactsCSVButton = ({
noPadding
closeOnOutsideClick={false}
className="overflow-auto"
size="xxl"
size="xl"
hideCloseButton>
<div className="sticky top-0 flex h-full flex-col rounded-lg">
<button
@@ -262,40 +285,53 @@ export const UploadContactsCSVButton = ({
) : null}
<div className="flex flex-col gap-8 px-6 py-4">
<div className="no-scrollbar max-h-[400px] overflow-auto rounded-md border-2 border-dashed border-slate-300 bg-slate-50 p-4">
{!csvResponse.length ? (
<label
htmlFor="file"
className={cn(
"relative flex cursor-pointer flex-col items-center justify-center rounded-lg hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800"
)}
// onDragOver={(e) => handleDragOver(e)}
// onDrop={(e) => handleDrop(e)}>
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<ArrowUpFromLineIcon className="h-6 text-slate-500" />
<p className={cn("mt-2 text-center text-sm text-slate-500")}>
<span className="font-semibold">{t("common.upload_input_description")}</span>
</p>
<input
type="file"
id={"file"}
name={"file"}
accept=".csv"
className="hidden"
onChange={handleFileUpload}
/>
<div className="flex flex-col gap-2">
<div className="no-scrollbar max-h-[400px] overflow-auto rounded-md border-2 border-dashed border-slate-300 bg-slate-50 p-4">
{!csvResponse.length ? (
<div>
<label
htmlFor="file"
className={cn(
"relative flex cursor-pointer flex-col items-center justify-center rounded-lg hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800"
)}
// onDragOver={(e) => handleDragOver(e)}
// onDrop={(e) => handleDrop(e)}>
>
<div className="flex flex-col items-center justify-center pb-6 pt-5">
<ArrowUpFromLineIcon className="h-6 text-slate-500" />
<p className={cn("mt-2 text-center text-sm text-slate-500")}>
<span className="font-semibold">{t("common.upload_input_description")}</span>
</p>
<input
type="file"
id={"file"}
name={"file"}
accept=".csv"
className="hidden"
onChange={handleFileUpload}
/>
</div>
</label>
</div>
</label>
) : (
<div className="flex flex-col items-center gap-8">
<h3 className="font-medium text-slate-500">
{t("environments.contacts.upload_contacts_modal_preview")}
</h3>
<div className="h-[300px] w-full overflow-auto rounded-md border border-slate-300">
<CsvTable data={[...csvResponse.slice(0, 11)]} />
) : (
<div className="flex flex-col items-center gap-8">
<h3 className="font-medium text-slate-500">
{t("environments.contacts.upload_contacts_modal_preview")}
</h3>
<div className="h-[300px] w-full overflow-auto rounded-md border border-slate-300">
<CsvTable data={[...csvResponse.slice(0, 11)]} />
</div>
</div>
</div>
)}
</div>
{!csvResponse.length && (
<p>
<a
onClick={handleDownloadExampleCSV}
className="cursor-pointer text-right text-sm text-slate-500">
{t("environments.contacts.upload_contacts_modal_download_example_csv")}{" "}
</a>
</p>
)}
</div>

View File

@@ -2,6 +2,7 @@ import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { Table } from "@tanstack/react-table";
import { MoveVerticalIcon, RefreshCcwIcon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { SelectedRowSettings } from "./selected-row-settings";
@@ -13,7 +14,7 @@ interface DataTableToolbarProps<T> {
deleteRows: (rowIds: string[]) => void;
type: "response" | "contact";
deleteAction: (id: string) => Promise<void>;
refreshContacts?: () => void;
refreshContacts?: () => Promise<void>;
}
export const DataTableToolbar = <T,>({
@@ -41,9 +42,19 @@ export const DataTableToolbar = <T,>({
tooltipContent={t("environments.contacts.contacts_table_refresh")}
shouldRender={true}>
<div
onClick={() => refreshContacts?.()}
onClick={async () => {
if (refreshContacts) {
try {
await refreshContacts();
toast.success(t("environments.contacts.contacts_table_refresh_success"));
} catch (err) {
console.error(err);
toast.error(t("environments.contacts.contacts_table_refresh_error"));
}
}
}}
className="cursor-pointer rounded-md border bg-white hover:border-slate-400">
<RefreshCcwIcon strokeWidth={1.5} className="m-1 h-6 w-6 p-0.5" />
<RefreshCcwIcon strokeWidth={1.5} className={cn("m-1 h-6 w-6 p-0.5")} />
</div>
</TooltipRenderer>
) : null}

View File

@@ -30,7 +30,7 @@ interface DialogContentProps
const sizeClassName = {
md: "sm:max-w-xl",
lg: "sm:max-w-[820px]",
xl: "sm:max-w-[960px]",
xl: "sm:max-w-[960px] sm:max-h-[640px]",
xxl: "sm:max-w-[1240px] sm:max-h-[760px]",
};

View File

@@ -69,7 +69,7 @@ export const StylingTabs = <T extends string | number>({
className="sr-only"
/>
<span className="text-slate-900">{option.label}</span>
<div>{option.icon}</div>
{option.icon && <div>{option.icon}</div>}
</label>
))}
</div>

View File

@@ -580,6 +580,8 @@
"contact_deleted_successfully": "Kontakt erfolgreich gelöscht",
"contact_not_found": "Kein solcher Kontakt gefunden",
"contacts_table_refresh": "Kontakte aktualisieren",
"contacts_table_refresh_error": "Beim Aktualisieren der Kontakte ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
"error_fetching_next_page_of_people_data": "Fehler beim Abrufen der nächsten Seite mit Kontaktdaten",
"error_fetching_people_data": "Fehler beim Abrufen der Kontaktdaten",
"fetching_user": "Benutzer wird geladen",
@@ -601,6 +603,7 @@
"upload_contacts_modal_attributes_should_be_mapped_to": "sollte zugeordnet werden zu",
"upload_contacts_modal_attributes_title": "Attribute",
"upload_contacts_modal_description": "Lade eine CSV hoch, um Kontakte mit Attributen schnell zu importieren",
"upload_contacts_modal_download_example_csv": "Beispiel-CSV herunterladen",
"upload_contacts_modal_duplicates_description": "Wie sollen wir vorgehen, wenn ein Kontakt bereits existiert?",
"upload_contacts_modal_duplicates_overwrite_description": "Überschreibt die bestehenden Kontakte",
"upload_contacts_modal_duplicates_overwrite_title": "Überschreiben",

View File

@@ -580,6 +580,8 @@
"contact_deleted_successfully": "Contact deleted successfully",
"contact_not_found": "No such contact found",
"contacts_table_refresh": "Refresh contacts",
"contacts_table_refresh_error": "Something went wrong while refreshing contacts, please try again",
"contacts_table_refresh_success": "Contacts refreshed successfully",
"error_fetching_next_page_of_people_data": "Error fetching next page of contacts data",
"error_fetching_people_data": "Error fetching contacts data",
"fetching_user": "Fetching user",
@@ -601,6 +603,7 @@
"upload_contacts_modal_attributes_should_be_mapped_to": "should be mapped to",
"upload_contacts_modal_attributes_title": "Attributes",
"upload_contacts_modal_description": "Upload a CSV to quickly import contacts with attributes",
"upload_contacts_modal_download_example_csv": "Download example CSV",
"upload_contacts_modal_duplicates_description": "How should we handle if a contact already exists in your contacts?",
"upload_contacts_modal_duplicates_overwrite_description": "Overwrites the existing contacts",
"upload_contacts_modal_duplicates_overwrite_title": "Overwrite",

View File

@@ -580,6 +580,8 @@
"contact_deleted_successfully": "Contact supprimé avec succès",
"contact_not_found": "Aucun contact trouvé",
"contacts_table_refresh": "Rafraîchir les contacts",
"contacts_table_refresh_error": "Une erreur s'est produite lors de la mise à jour des contacts. Veuillez réessayer.",
"contacts_table_refresh_success": "Contacts rafraîchis avec succès",
"error_fetching_next_page_of_people_data": "Erreur lors de la récupération de la page suivante des données de contacts",
"error_fetching_people_data": "Erreur lors de la récupération des données de contacts",
"fetching_user": "Récupération de l'utilisateur",
@@ -601,6 +603,7 @@
"upload_contacts_modal_attributes_should_be_mapped_to": "devrait être mappé à",
"upload_contacts_modal_attributes_title": "Attributs",
"upload_contacts_modal_description": "Téléchargez un CSV pour importer rapidement des contacts avec des attributs.",
"upload_contacts_modal_download_example_csv": "Télécharger un exemple de CSV",
"upload_contacts_modal_duplicates_description": "Comment devrions-nous procéder si un contact existe déjà dans vos contacts ?",
"upload_contacts_modal_duplicates_overwrite_description": "Écrase les contacts existants",
"upload_contacts_modal_duplicates_overwrite_title": "Sélectionner",

View File

@@ -580,6 +580,8 @@
"contact_deleted_successfully": "Contato excluído com sucesso",
"contact_not_found": "Nenhum contato encontrado",
"contacts_table_refresh": "Atualizar contatos",
"contacts_table_refresh_error": "Ocorreu um erro ao atualizar os contatos. Por favor, tente novamente.",
"contacts_table_refresh_success": "Contatos atualizados com sucesso",
"error_fetching_next_page_of_people_data": "Erro ao buscar a próxima página de dados de contatos",
"error_fetching_people_data": "Erro ao buscar os dados de contatos",
"fetching_user": "Carregando usuário",
@@ -601,6 +603,7 @@
"upload_contacts_modal_attributes_should_be_mapped_to": "deve ser mapeado para",
"upload_contacts_modal_attributes_title": "Atributos",
"upload_contacts_modal_description": "Faça upload de um CSV para importar contatos com atributos rapidamente",
"upload_contacts_modal_download_example_csv": "Baixar exemplo de CSV",
"upload_contacts_modal_duplicates_description": "O que devemos fazer se um contato já existir nos seus contatos?",
"upload_contacts_modal_duplicates_overwrite_description": "Sobrescreve os contatos existentes",
"upload_contacts_modal_duplicates_overwrite_title": "Sobrescrever",