Reconmap/web-client

View on GitHub
src/components/clients/Details.jsx

Summary

Maintainability
F
5 days
Test Coverage
import {
    Button,
    ButtonGroup,
    Tab,
    TabList,
    TabPanel,
    TabPanels,
    Tabs,
} from "@chakra-ui/react";
import NativeInput from "components/form/NativeInput";
import NativeSelect from "components/form/NativeSelect";
import PageTitle from "components/logic/PageTitle";
import RestrictedComponent from "components/logic/RestrictedComponent";
import ProjectsTable from "components/projects/Table";
import EmptyField from "components/ui/EmptyField";
import MailLink from "components/ui/MailLink";
import TelephoneLink from "components/ui/TelephoneLink";
import TimestampsSection from "components/ui/TimestampsSection";
import DeleteIconButton from "components/ui/buttons/DeleteIconButton";
import LinkButton from "components/ui/buttons/Link";
import NoResultsTableRow from "components/ui/tables/NoResultsTableRow";
import { actionCompletedToast, errorToast } from "components/ui/toast";
import UserLink from "components/users/Link";
import Contact from "models/Contact";
import { useEffect, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import secureApiFetch from "services/api";
import Breadcrumb from "../ui/Breadcrumb";
import ExternalLink from "../ui/ExternalLink";
import { IconBriefcase } from "../ui/Icons";
import Title from "../ui/Title";
import DeleteButton from "../ui/buttons/Delete";
import useDelete from "./../../hooks/useDelete";
import useFetch from "./../../hooks/useFetch";
import Loading from "./../ui/Loading";

const ClientProjectsTab = ({ clientId }) => {
    const [projects] = useFetch(`/projects?clientId=${clientId}`);

    if (!projects) return <Loading />;

    if (projects.length === 0) {
        return (
            <div>
                This client has no projects. You can see all projects and their
                clients <Link to="/projects">here</Link>.
            </div>
        );
    }

    return (
        <div>
            <ProjectsTable projects={projects} showClientColumn={false} />
        </div>
    );
};

const ClientDetails = () => {
    const { clientId } = useParams();
    const navigate = useNavigate();

    const [client] = useFetch(`/clients/${clientId}`);
    const [contacts, fetchContacts] = useFetch(`/clients/${clientId}/contacts`);

    const [contact, setContact] = useState({ ...Contact });

    const onContactFormChange = (ev) => {
        setContact({ ...contact, [ev.target.name]: ev.target.value });
    };

    const deleteClient = useDelete(`/clients/`);
    const [logo, setLogo] = useState(null);
    const [smallLogo, setSmallLogo] = useState(null);

    const handleDelete = async () => {
        const confirmed = await deleteClient(clientId);
        if (confirmed) navigate("/clients");
    };

    const onFormSubmit = (ev) => {
        ev.preventDefault();

        secureApiFetch(`/clients/${clientId}/contacts`, {
            method: "POST",
            body: JSON.stringify(contact),
        }).then((resp) => {
            if (resp.status === 201) {
                setContact({ ...Contact });
                fetchContacts();
                actionCompletedToast(`The contact has been added.`);
            } else {
                errorToast(
                    "The contact could not be saved. Review the form data or check the application logs.",
                );
            }
        });
    };

    const onContactDelete = (contactId) => {
        secureApiFetch(`/contacts/${contactId}`, { method: "DELETE" })
            .then(() => {
                fetchContacts();
                actionCompletedToast("The contact has been deleted.");
            })
            .catch((err) => console.error(err));
    };

    useEffect(() => {
        if (client) {
            if (client.small_logo_attachment_id) {
                downloadAndDisplayLogo(
                    client.small_logo_attachment_id,
                    "small_logo",
                );
            }

            if (client.logo_attachment_id) {
                downloadAndDisplayLogo(client.logo_attachment_id, "logo");
            }
        }
    }, [client]);

    const downloadAndDisplayLogo = (logoId, type) => {
        secureApiFetch(`/attachments/${logoId}`, { method: "GET" })
            .then((resp) => {
                const contentDispositionHeader = resp.headers.get(
                    "Content-Disposition",
                );
                const filenameRe = new RegExp(/filename="(.*)";/);
                const filename = filenameRe.exec(contentDispositionHeader)[1];
                return Promise.all([resp.blob(), filename]);
            })
            .then((values) => {
                const blob = values[0];
                const url = URL.createObjectURL(blob);
                if (type === "small_logo") {
                    setSmallLogo(url);
                } else {
                    setLogo(url);
                }
            });
    };

    if (!client) {
        return <Loading />;
    }

    return (
        <div>
            <div className="heading">
                <Breadcrumb>
                    <Link to="/clients">Clients</Link>
                </Breadcrumb>
                <ButtonGroup>
                    <RestrictedComponent
                        roles={["administrator", "superuser", "user"]}
                    >
                        <LinkButton href={`/clients/${client.id}/edit`}>
                            Edit
                        </LinkButton>
                        <DeleteButton onClick={handleDelete} />
                    </RestrictedComponent>
                </ButtonGroup>
            </div>
            <article>
                <div>
                    <PageTitle value={`${client.name} client`} />
                    <Title
                        type="Client"
                        title={client.name}
                        icon={<IconBriefcase />}
                    />
                </div>

                <Tabs isLazy>
                    <TabList>
                        <Tab>Details</Tab>
                        <Tab>Contacts</Tab>
                        <Tab>Projects</Tab>
                    </TabList>
                    <TabPanels>
                        <TabPanel>
                            <div className="grid grid-two">
                                <div>
                                    <h4>Properties</h4>

                                    <dl>
                                        <dt>Address</dt>
                                        <dd>{client.address ?? "-"}</dd>

                                        <dt>URL</dt>
                                        <dd>
                                            <ExternalLink href={client.url}>
                                                {client.url}
                                            </ExternalLink>
                                        </dd>
                                    </dl>

                                    <h4>Branding</h4>

                                    <dl>
                                        <dt>Main logo</dt>
                                        <dd>
                                            {logo ? (
                                                <img
                                                    src={logo}
                                                    alt="The main logo"
                                                />
                                            ) : (
                                                <EmptyField />
                                            )}
                                        </dd>

                                        <dt>Small logo</dt>
                                        <dd>
                                            {smallLogo ? (
                                                <img
                                                    src={smallLogo}
                                                    alt="The smaller version of the logo"
                                                />
                                            ) : (
                                                <EmptyField />
                                            )}
                                        </dd>
                                    </dl>
                                </div>

                                <div>
                                    <h4>Relations</h4>
                                    <dl>
                                        <dt>Created by</dt>
                                        <dd>
                                            <UserLink
                                                userId={client.creator_uid}
                                            >
                                                {client.creator_full_name}
                                            </UserLink>
                                        </dd>
                                    </dl>

                                    <TimestampsSection entity={client} />
                                </div>
                            </div>
                        </TabPanel>

                        <TabPanel>
                            <h4>Contacts</h4>

                            {contacts && (
                                <>
                                    <form onSubmit={onFormSubmit}>
                                        <table>
                                            <thead>
                                                <tr>
                                                    <th>Kind</th>
                                                    <th>Name</th>
                                                    <th>Role</th>
                                                    <th>Email</th>
                                                    <th>Phone</th>
                                                    <th>&nbsp;</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <tr>
                                                    <td>
                                                        <NativeSelect
                                                            name="kind"
                                                            onChange={
                                                                onContactFormChange
                                                            }
                                                            value={
                                                                contact.kind ||
                                                                ""
                                                            }
                                                            isRequired
                                                        >
                                                            <option value="general">
                                                                General
                                                            </option>
                                                            <option value="technical">
                                                                Technical
                                                            </option>
                                                            <option value="billing">
                                                                Billing
                                                            </option>
                                                        </NativeSelect>
                                                    </td>
                                                    <td>
                                                        <NativeInput
                                                            type="text"
                                                            name="name"
                                                            onChange={
                                                                onContactFormChange
                                                            }
                                                            value={
                                                                contact.name ||
                                                                ""
                                                            }
                                                            required
                                                        />
                                                    </td>
                                                    <td>
                                                        <NativeInput
                                                            type="text"
                                                            name="role"
                                                            onChange={
                                                                onContactFormChange
                                                            }
                                                            value={
                                                                contact.role ||
                                                                ""
                                                            }
                                                        />
                                                    </td>
                                                    <td>
                                                        <NativeInput
                                                            type="email"
                                                            name="email"
                                                            onChange={
                                                                onContactFormChange
                                                            }
                                                            value={
                                                                contact.email ||
                                                                ""
                                                            }
                                                            required
                                                        />
                                                    </td>
                                                    <td>
                                                        <NativeInput
                                                            type="tel"
                                                            name="phone"
                                                            onChange={
                                                                onContactFormChange
                                                            }
                                                            value={
                                                                contact.phone ||
                                                                ""
                                                            }
                                                        />
                                                    </td>
                                                    <td>
                                                        <Button type="submit">
                                                            Add
                                                        </Button>
                                                    </td>
                                                </tr>
                                                {0 === contacts.length && (
                                                    <NoResultsTableRow
                                                        numColumns={6}
                                                    />
                                                )}
                                                {contacts.map((contact) => (
                                                    <>
                                                        <tr key={contact.id}>
                                                            <td>
                                                                {contact.kind}
                                                            </td>
                                                            <td>
                                                                {contact.name}
                                                            </td>
                                                            <td>
                                                                {contact.role}
                                                            </td>
                                                            <td>
                                                                <MailLink
                                                                    email={
                                                                        contact.email
                                                                    }
                                                                />
                                                            </td>
                                                            <td>
                                                                <TelephoneLink
                                                                    number={
                                                                        contact.phone
                                                                    }
                                                                />
                                                            </td>
                                                            <td>
                                                                <DeleteIconButton
                                                                    onClick={onContactDelete.bind(
                                                                        this,
                                                                        contact.id,
                                                                    )}
                                                                />
                                                            </td>
                                                        </tr>
                                                    </>
                                                ))}
                                            </tbody>
                                        </table>
                                    </form>
                                </>
                            )}
                        </TabPanel>
                        <TabPanel>
                            <ClientProjectsTab clientId={clientId} />
                        </TabPanel>
                    </TabPanels>
                </Tabs>
            </article>
        </div>
    );
};

export default ClientDetails;