Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/entities/rows/RowActivity.tsx
T

199 lines
8.6 KiB
TypeScript

import { FormEvent, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Form, useLocation, useSubmit, useNavigation } from "react-router";
import { DefaultLogActions } from "~/application/dtos/shared/DefaultLogActions";
import UserAvatarBadge from "~/components/core/users/UserAvatarBadge";
import UserBadge from "~/components/core/users/UserBadge";
import CalendarFilledIcon from "~/components/ui/icons/CalendarFilledIcon";
import ChatAltIcon from "~/components/ui/icons/ChatAltIcon";
import PencilIcon from "~/components/ui/icons/PencilIcon";
import QuestionMarkFilledIcon from "~/components/ui/icons/QuestionMarkFilledIcon";
import { LogWithDetails } from "~/utils/db/logs.db.server";
import RowLogComment from "./RowLogComment";
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
import clsx from "clsx";
import { Button } from "~/components/ui/button";
import { appendCSRFToken } from "~/utils/csrf";
import { useLocaleFormatters } from "~/locale/format";
interface Props {
items: LogWithDetails[];
hasActivity?: boolean;
hasComments?: boolean;
onSubmit?: (formData: FormData) => void;
withTitle?: boolean;
autoFocus?: boolean;
}
export default function RowActivity({ items, hasActivity = true, hasComments, onSubmit, withTitle = true, autoFocus }: Props) {
const { t } = useTranslation();
const formatters = useLocaleFormatters();
const appOrAdminData = useAppOrAdminData();
const transition = useNavigation();
const submit = useSubmit();
const isAdding = transition.state === "submitting" && transition.formData?.get("action") === "comment";
const formRef = useRef<HTMLFormElement>(null);
const location = useLocation();
useEffect(() => {
formRef.current?.reset();
}, [isAdding]);
const sortedItems = () => {
const filteredItems = items.filter((item) => {
if (item.commentId && !hasComments) {
return false;
}
return true;
});
return filteredItems.slice().sort((x, y) => {
if (x.createdAt && y.createdAt) {
return x.createdAt > y.createdAt ? 1 : -1;
}
return 1;
});
};
function getActionDescription(item: LogWithDetails) {
return item.action;
}
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.stopPropagation();
e.preventDefault();
const formData = new FormData(e.currentTarget);
appendCSRFToken(formData);
if (onSubmit !== undefined) {
onSubmit(formData);
} else {
submit(formData, {
method: "post",
action: location.pathname + location.search,
});
}
}
return (
<section className="relative">
<div className="space-y-3">
<div className="divide-border divide-y ">
{withTitle && (
<div className="pb-2">
<h2 id="activity-title" className="text-foreground text-sm font-medium">
{t("app.shared.activity.title")}
</h2>
</div>
)}
<div className={clsx("space-y-6 text-xs", withTitle && "pt-4")}>
{/* Activity feed*/}
<div className="">
<ul className="-mb-8 space-y-6 pb-6">
{sortedItems().map((item, idx) => (
<li key={item.id}>
<div className="relative">
{idx !== sortedItems().length - 1 ? <span className="absolute left-5 top-5 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true" /> : null}
<div className="relative flex items-start space-x-3">
{item.comment ? (
<>{hasComments && <RowLogComment item={item} />}</>
) : (
<>
<div>
<div className="relative px-1">
<div className="relative">
<UserAvatarBadge className="h-9 w-9" avatar={item.user?.avatar} />
<span className="bg-secondary absolute -bottom-0.5 -right-1 rounded-tl px-0.5 py-px">
{/* <TagIcon className="h-4 w-4 text-muted-foreground" aria-hidden="true" /> */}
{item.action === DefaultLogActions.Created ? (
<CalendarFilledIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
) : item.action === DefaultLogActions.Updated ? (
<PencilIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
) : (
<QuestionMarkFilledIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
)}
</span>
</div>
</div>
</div>
<div className="min-w-0 flex-1 py-0">
<div className=" text-muted-foreground">
<span className="mr-0.5 flex items-center space-x-1 text-sm">
<div className="text-foreground font-medium">
{item.user && (
<span>
<UserBadge item={item.user} withEmail={false} />
</span>
)}
</div>
</span>
<span className="mr-0.5" title={JSON.stringify(item.details) !== JSON.stringify("{}") ? item.details?.toString() : ""}>
{getActionDescription(item)}
</span>
<span className="whitespace-nowrap pt-1">
<time dateTime={new Date(item.createdAt).toISOString()} title={formatters.dateTime(item.createdAt, { timeStyle: "medium" })}>
{formatters.relativeDate(item.createdAt)}
</time>
</span>
</div>
</div>
</>
)}
</div>
</div>
</li>
))}
</ul>
</div>
{hasComments && (
<div className="">
<div className="flex space-x-3">
<div className="shrink-0">
<div className="relative px-1">
<UserAvatarBadge className="h-9 w-9" avatar={appOrAdminData.user?.avatar} />
<span className="bg-secondary absolute -bottom-0.5 -right-1 rounded-tl px-0.5 py-px">
<ChatAltIcon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
</span>
</div>
</div>
<div className="min-w-0 flex-1">
<Form ref={formRef} onSubmit={handleSubmit} method="post" className="space-y-2">
<div>
<input hidden readOnly name="action" value="comment" />
<label htmlFor="comment" className="sr-only">
{t("shared.comment")}
</label>
<textarea
autoFocus={autoFocus}
required
id="comment"
name="comment"
rows={3}
className={clsx(
"border-border block w-full rounded-md border shadow-2xs focus:border-gray-900 focus:ring-gray-900 sm:text-sm",
(isAdding || !appOrAdminData.user) && "bg-secondary/90 cursor-not-allowed"
)}
placeholder={t("shared.addComment")}
defaultValue={""}
disabled={isAdding || !appOrAdminData.user}
/>
</div>
<div className="flex items-center justify-end space-x-4">
<Button variant="outline" disabled={isAdding || !appOrAdminData.user} type="submit">
{t("shared.comment")}
</Button>
</div>
</Form>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</section>
);
}