Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/basic asset reminders #1540

Open
wants to merge 85 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
bb9038d
feat(asset-reminder): create UI for asset reminders
rockingrohit9639 Dec 17, 2024
19f8db6
feat(asset-reminders): create schema for asset reminder
rockingrohit9639 Dec 17, 2024
1dbb296
feat(asset-reminders): create schema for asset reminder
rockingrohit9639 Dec 17, 2024
618894f
feat(asset-reminder): create action to handle creating reminder
rockingrohit9639 Dec 17, 2024
eb3b73a
Merge branch 'feature/basic-asset-reminders' of github.com:rockingroh…
rockingrohit9639 Dec 17, 2024
68fc070
cMerge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/bas…
rockingrohit9639 Dec 17, 2024
2fbe8a7
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Dec 18, 2024
1e55fd2
feat(asset-reminder): create page to display list of reminders
rockingrohit9639 Dec 18, 2024
bedc2d0
feat(asset-reminders): create frontend for edit reminder
rockingrohit9639 Dec 18, 2024
d467b06
feat(asset-reminder): create backend to edit reminder
rockingrohit9639 Dec 18, 2024
89e7bab
feat(asset-reminder): create feature to delete a reminder
rockingrohit9639 Dec 18, 2024
cf455ee
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Dec 19, 2024
dbb2ec2
feat(asset-reminders): add validation to select teamMember with user …
rockingrohit9639 Dec 19, 2024
629758e
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Dec 20, 2024
84db275
feat(asset-reminders): create email template
rockingrohit9639 Dec 20, 2024
208cf6c
feat(sentry-erros): create scheduler for asset reminders
rockingrohit9639 Dec 20, 2024
fe443ea
feat(asset-reminder): create function to cancel asset reminder scheduler
rockingrohit9639 Dec 20, 2024
e4464f3
feat(asset-reminder): rescheduling reminder on edit
rockingrohit9639 Dec 20, 2024
9189576
feat(asset-reminder): move asset-reminder in separeate module
rockingrohit9639 Dec 30, 2024
7b830c4
feat(asset-reminder): move asset-reminder in separeate module
rockingrohit9639 Dec 30, 2024
400c98b
fix(migration): remove DROP INDEX from asset reminder migration file
rockingrohit9639 Dec 30, 2024
810e93c
feat(asset-reminder): create card to show 2 reminder on asset overvie…
rockingrohit9639 Dec 30, 2024
3a391ac
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Dec 31, 2024
0e3e8e2
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 2, 2025
0576367
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 2, 2025
d2493ed
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Jan 3, 2025
57b9ac8
feat(asset-reminder): update how we handle alertDateTime in AssetRemi…
rockingrohit9639 Jan 3, 2025
131b9e0
feat(assets-reminder): showing owner and admin users in TeamMember
rockingrohit9639 Jan 3, 2025
35ccba9
feat(asset-reminder): add status column in table
rockingrohit9639 Jan 3, 2025
d57bcb9
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 6, 2025
5746a46
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Jan 6, 2025
bb67611
Merge branch 'feature/basic-asset-reminders' of github.com:rockingroh…
rockingrohit9639 Jan 6, 2025
5068963
feat(asset-reminder): fix scheduler reference error
rockingrohit9639 Jan 6, 2025
9d438de
feat(asset-reminder): allow edit for pending reminder only, fix forwa…
rockingrohit9639 Jan 6, 2025
5ade3d6
feat(asset-reminder): fix issue with alertDateTime on edit
rockingrohit9639 Jan 6, 2025
b64ece9
feat(asset-reminder): create separate API for fetching teamMembers fo…
rockingrohit9639 Jan 6, 2025
954ff5c
feat(asset-reminder): add badge for status column in alerts table
rockingrohit9639 Jan 6, 2025
f7ed782
feat(asset-reminders): fix redirect issue after creating reminder
rockingrohit9639 Jan 7, 2025
870b7d7
feat(asset-reminder): fix minor issues
rockingrohit9639 Jan 7, 2025
3df7d24
feat(asset-reminders): fix email template and minor issue fixes
rockingrohit9639 Jan 7, 2025
45e05bf
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 7, 2025
9df6ca9
small style adjustments + changing order of reminders on list
DonKoko Jan 7, 2025
cfb02ef
importing scheduler inside asset-reminder workers file
DonKoko Jan 7, 2025
ed0011d
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 7, 2025
54814e1
feat(asset-reminders): centered logo in email template
rockingrohit9639 Jan 8, 2025
b26819b
Merge branch 'feature/basic-asset-reminders' of github.com:rockingroh…
rockingrohit9639 Jan 8, 2025
e9506ae
feat(asset-reminders): rename all alerts to reminders
rockingrohit9639 Jan 8, 2025
6f1c1db
feat(asset-reminders): create reminders index page
rockingrohit9639 Jan 8, 2025
b4815aa
feat(asset-reminders): add search filter in reminders tab
rockingrohit9639 Jan 9, 2025
23e5fbc
feat(asset-reminders): create function to resolve reminders actions
rockingrohit9639 Jan 9, 2025
a6f7a3c
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 9, 2025
d30d6b2
updates:
DonKoko Jan 9, 2025
2c5b6f2
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 9, 2025
8203451
updating search logic to fit to new approach
DonKoko Jan 9, 2025
7e9b2c6
feat(asset-reminders): update empty state content
rockingrohit9639 Jan 9, 2025
0a1715c
Merge branch 'feature/basic-asset-reminders' of github.com:rockingroh…
rockingrohit9639 Jan 9, 2025
c8f605a
feat(asset-reminder): handling the case when teamMember is removed fr…
rockingrohit9639 Jan 10, 2025
bf16661
feat(asset-reminders): showing an alert message on reminder teamMembe…
rockingrohit9639 Jan 10, 2025
098fa24
feat(asset-reminders): showing only upcoming reminders on asset overv…
rockingrohit9639 Jan 10, 2025
683ced3
feat(asset-reminders): make sort-by component generic and use on remi…
rockingrohit9639 Jan 10, 2025
78eb8a1
feat(asset-reminders): add indexes to optimize search
rockingrohit9639 Jan 10, 2025
bbb69a6
feat(asset-reminders): showing user's email in team member selector
rockingrohit9639 Jan 10, 2025
ec4ed6c
feat(asset-reminder): creating activity note when a reminder is created
rockingrohit9639 Jan 10, 2025
03822be
feat(asset-reminders): update condition to show warning for teamMember
rockingrohit9639 Jan 10, 2025
dd907f2
feat(asset-reminders): cancelling reminder schedulers when asset is d…
rockingrohit9639 Jan 13, 2025
f004649
feat(asset-reminders): create upcoming reminder column in advanced as…
rockingrohit9639 Jan 13, 2025
d871318
feat(asset-reminder): add index on alertDateTime in AssetReminder
rockingrohit9639 Jan 13, 2025
df8b063
feat(asset-reminder): add link to knowledge base in SetOrEditReminder
rockingrohit9639 Jan 13, 2025
9b5fc7e
feat(asset-reminder): remove unused import
rockingrohit9639 Jan 13, 2025
73a28c8
query and indexes adjustment
DonKoko Jan 14, 2025
26eaf61
small fix for an issue with asset index
DonKoko Jan 14, 2025
7f461b4
Merge branch 'main' into feature/basic-asset-reminders
rockingrohit9639 Jan 14, 2025
ff543ad
feat(asset-reminders): add sorting filter on asset reminders page
rockingrohit9639 Jan 15, 2025
168afb0
feat(asset-reminders): add tooltip for upcoming reminder column
rockingrohit9639 Jan 15, 2025
d39192e
feat(asset-reminders): showing date and reduce message length on remi…
rockingrohit9639 Jan 15, 2025
05765c8
feat(asset-reminder): create note when reminder is sent
rockingrohit9639 Jan 15, 2025
9008661
feat(asset-reminder): add LInk on team member
rockingrohit9639 Jan 15, 2025
6b1a107
feat(asset-reminder): fix overflow issue on reminders table
rockingrohit9639 Jan 15, 2025
6f380f9
feat(asset-remidners): change posisition of reminder date in reminder…
rockingrohit9639 Jan 15, 2025
9ecce9d
Merge branch 'main' of github.com:Shelf-nu/shelf.nu into feature/basi…
rockingrohit9639 Jan 15, 2025
ae31ecf
feat(asset-reminders): create separate function to format assets remi…
rockingrohit9639 Jan 15, 2025
a1c509e
Merge branch 'main' into feature/basic-asset-reminders
DonKoko Jan 15, 2025
de0d0c5
feat(asset-reminder): add margin in shelf logo
rockingrohit9639 Jan 16, 2025
093e3ea
Merge branch 'main' into feature/basic-asset-reminders
rockingrohit9639 Jan 16, 2025
30a574f
Merge branch 'main' into feature/basic-asset-reminders
rockingrohit9639 Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions app/components/asset-reminder/actions-dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useState } from "react";
import type { Prisma } from "@prisma/client";
import { PencilIcon } from "lucide-react";
import { VerticalDotsIcon } from "~/components/icons/library";
import { Button } from "~/components/shared/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/shared/dropdown";
import type { ASSET_REMINDER_INCLUDE_FIELDS } from "~/modules/asset-reminder/fields";
import DeleteReminder from "./delete-reminder";
import SetOrEditReminderDialog from "./set-or-edit-reminder-dialog";
import When from "../when/when";

type ActionsDropdownProps = {
reminder: Prisma.AssetReminderGetPayload<{
include: typeof ASSET_REMINDER_INCLUDE_FIELDS;
}>;
};

export default function ActionsDropdown({ reminder }: ActionsDropdownProps) {
const [isDropdownOpem, setIsDropdownOpen] = useState(false);
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);

const now = new Date();
const isPending = now < new Date(reminder.alertDateTime);

return (
<DropdownMenu
open={isDropdownOpem}
onOpenChange={setIsDropdownOpen}
modal={false}
>
<DropdownMenuTrigger className="px-2">
<VerticalDotsIcon />
</DropdownMenuTrigger>

<DropdownMenuContent align="end" className="order p-1.5 ">
<When truthy={isPending}>
<DropdownMenuItem asChild>
<Button
role="button"
variant="link"
className="cursor-pointer justify-start text-gray-700 hover:text-gray-700"
width="full"
onClick={() => {
setIsDropdownOpen(false);
setIsEditDialogOpen(true);
}}
aria-label="Edit Reminder"
>
<span className="flex items-center gap-2">
<PencilIcon className="size-4" /> Edit
</span>
</Button>
</DropdownMenuItem>
</When>
<DropdownMenuItem asChild>
<DeleteReminder reminder={reminder} />
</DropdownMenuItem>
</DropdownMenuContent>

<SetOrEditReminderDialog
reminder={{
id: reminder.id,
name: reminder.name,
message: reminder.message,
alertDateTime: reminder.alertDateTime,
teamMembers: reminder.teamMembers.map((tm) => tm.id),
}}
open={isEditDialogOpen}
onClose={() => {
setIsEditDialogOpen(false);
}}
/>
</DropdownMenu>
);
}
78 changes: 78 additions & 0 deletions app/components/asset-reminder/delete-reminder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { forwardRef } from "react";
import type { Prisma } from "@prisma/client";
import { Form, useNavigation } from "@remix-run/react";
import { TrashIcon } from "lucide-react";
import { Button } from "~/components/shared/button";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "~/components/shared/modal";
import type { ASSET_REMINDER_INCLUDE_FIELDS } from "~/modules/asset-reminder/fields";
import { isFormProcessing } from "~/utils/form";

type DeleteReminderProps = {
reminder: Prisma.AssetReminderGetPayload<{
include: typeof ASSET_REMINDER_INCLUDE_FIELDS;
}>;
};

const DeleteReminder = forwardRef<HTMLButtonElement, DeleteReminderProps>(
function ({ reminder }, ref) {
const navigation = useNavigation();
const disabled = isFormProcessing(navigation.state);

return (
<AlertDialog>
<AlertDialogTrigger
ref={ref}
className="flex w-full items-center gap-2 rounded px-2 py-1.5 font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-700"
>
<TrashIcon className="size-4" /> Delete
</AlertDialogTrigger>

<AlertDialogContent>
<AlertDialogHeader>
<div className="mx-auto md:m-0">
<span className="flex size-12 items-center justify-center rounded-full bg-error-50 p-2 text-error-600">
<TrashIcon />
</span>
</div>
<AlertDialogTitle>Delete {reminder.name}</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete this reminder? This action cannot
be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<div className="flex justify-center gap-2">
<AlertDialogCancel disabled={disabled} asChild>
<Button variant="secondary">Cancel</Button>
</AlertDialogCancel>

<Form method="delete">
<input type="hidden" value={reminder.id} name="id" />
<input type="hidden" value="delete-reminder" name="intent" />

<Button
disabled={disabled}
className="border-error-600 bg-error-600 hover:border-error-800 hover:!bg-error-800"
>
Delete
</Button>
</Form>
</div>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
);

DeleteReminder.displayName = "DeleteReminder";
export default DeleteReminder;
90 changes: 90 additions & 0 deletions app/components/asset-reminder/reminder-team-members.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Prisma } from "@prisma/client";
import { Link } from "@remix-run/react";
import { tw } from "~/utils/tw";
import { resolveTeamMemberName } from "~/utils/user";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../shared/tooltip";
import When from "../when/when";

type ReminderTeamMembersProps = {
className?: string;
style?: React.CSSProperties;
teamMembers: Prisma.TeamMemberGetPayload<{
select: {
id: true;
name: true;
user: {
select: {
id: true;
firstName: true;
lastName: true;
profilePicture: true;
};
};
};
}>[];
imgClassName?: string;
extraContent?: React.ReactNode;
isAlreadySent?: boolean;
};

export default function ReminderTeamMembers({
className,
style,
teamMembers,
imgClassName,
extraContent,
isAlreadySent = false,
}: ReminderTeamMembersProps) {
return (
<div className={tw("flex items-center", className)} style={style}>
{teamMembers.map((teamMember) => {
const isAccessRevoed = !teamMember.user;

return (
<TooltipProvider key={teamMember.id}>
<Tooltip>
<TooltipTrigger>
<Link
to={`/settings/team/users/${teamMember?.user?.id}/assets`}
className={tw(
"-ml-1 flex size-6 shrink-0 items-center justify-center overflow-hidden rounded border border-white",
imgClassName,
isAccessRevoed && "border-error-500"
)}
>
<img
alt={teamMember.name}
className="size-full object-cover"
src={
teamMember?.user?.profilePicture ??
"/static/images/default_pfp.jpg"
}
/>
</Link>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-72">
<p>{resolveTeamMemberName(teamMember, true)}</p>

<When truthy={isAccessRevoed && !isAlreadySent}>
<p className="mt-2 text-error-500">
This team member has been removed from the workspace. As a
fallback the reminder email will be sent to the workspace
Owner. You can always edit the reminder to assign it to a
different user.
</p>
</When>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})}

{extraContent}
</div>
);
}
148 changes: 148 additions & 0 deletions app/components/asset-reminder/reminders-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useState } from "react";
import type { Prisma } from "@prisma/client";
import colors from "tailwindcss/colors";
import type { ASSET_REMINDER_INCLUDE_FIELDS } from "~/modules/asset-reminder/fields";
import { List } from "../list";
import ReminderTeamMembers from "./reminder-team-members";
import SetOrEditReminderDialog from "./set-or-edit-reminder-dialog";
import { ListContentWrapper } from "../list/content-wrapper";
import { Filters } from "../list/filters";
import { SortBy } from "../list/filters/sort-by";
import { Badge } from "../shared/badge";
import { Button } from "../shared/button";
import { Td, Th } from "../table";
import ActionsDropdown from "./actions-dropdown";
import When from "../when/when";

type RemindersTableProps = {
isAssetReminderPage?: boolean;
};

export const REMINDERS_SORTING_OPTIONS = {
name: "Name",
alertDateTime: "Alert Time",
createdAt: "Date Created",
updatedAt: "Date Updated",
} as const;

export default function RemindersTable({
isAssetReminderPage,
}: RemindersTableProps) {
const [isReminderDialogOpen, setIsReminderDialogOpen] = useState(false);

const emptyStateTitle = isAssetReminderPage
? "No reminders for this asset"
: "No reminders created yet.";

return (
<ListContentWrapper className="mb-4">
<Filters
slots={{
"right-of-search": (
<SortBy
sortingOptions={REMINDERS_SORTING_OPTIONS}
defaultSortingBy="alertDateTime"
/>
),
}}
/>

<List
className="overflow-x-hidden"
ItemComponent={ListContent}
customEmptyStateContent={{
title: emptyStateTitle,
text: (
<p>
What are you waiting for? Create your first{" "}
{isAssetReminderPage ? (
<Button
variant="link"
onClick={() => {
setIsReminderDialogOpen(true);
}}
>
reminder
</Button>
) : (
"reminder"
)}{" "}
now!
</p>
),
}}
headerChildren={
<>
<Th>Message</Th>
<When truthy={!isAssetReminderPage}>
<Td>Asset</Td>
</When>
<Th>Alert Date</Th>
<Th>Status</Th>
<Th>Users</Th>
</>
}
extraItemComponentProps={{ isAssetReminderPage }}
/>

<SetOrEditReminderDialog
open={isReminderDialogOpen}
onClose={() => {
setIsReminderDialogOpen(false);
}}
/>
</ListContentWrapper>
);
}

function ListContent({
item,
extraProps,
}: {
item: Prisma.AssetReminderGetPayload<{
include: typeof ASSET_REMINDER_INCLUDE_FIELDS;
}> & { displayDate: string };
extraProps: { isAssetReminderPage: boolean };
}) {
const now = new Date();
const status =
now < new Date(item.alertDateTime) ? "Pending" : "Reminder sent";

return (
<>
<Td className="md:min-w-60">{item.name}</Td>
<Td className="max-w-62 md:max-w-96">{item.message}</Td>
<When truthy={!extraProps.isAssetReminderPage}>
<Td>
<Button
className="hover:underline"
to={`/assets/${item.asset.id}/overview`}
target="_blank"
variant={"link-gray"}
>
{item.asset.title}
</Button>
</Td>
</When>
<Td>{item.displayDate}</Td>
<Td>
<Badge
color={
status === "Pending" ? colors.yellow["500"] : colors.green["500"]
}
>
{status}
</Badge>
</Td>
<Td>
<ReminderTeamMembers
teamMembers={item.teamMembers}
isAlreadySent={status === "Reminder sent"}
/>
</Td>
<Td>
<ActionsDropdown reminder={item} />
</Td>
</>
);
}
Loading