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/qr column #1563

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 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
b463fb5
feat(qr-column): create qrId column with qr code preview
rockingrohit9639 Jan 8, 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;
184 changes: 184 additions & 0 deletions app/components/asset-reminder/set-or-edit-reminder-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { useEffect } from "react";
import { Form, useNavigation } from "@remix-run/react";
import { Link, useLocation, useSearchParams } from "react-router-dom";
import { useZorm } from "react-zorm";
import { z } from "zod";
import Input from "~/components/forms/input";
import { Button } from "~/components/shared/button";
import { Separator } from "~/components/shared/separator";
import { dateForDateTimeInputValue } from "~/utils/date-fns";
import { isFormProcessing } from "~/utils/form";
import TeamMembersSelector from "./team-members-selector";
import { Dialog, DialogPortal } from "../layout/dialog";

export const setReminderSchema = z.object({
name: z.string().min(1, "Please enter name."),
message: z.string().min(1, "Please enter message."),
alertDateTime: z.coerce.date().min(new Date()),
teamMembers: z
.array(z.string())
.min(1, "Please select at least one team member"),
redirectTo: z.string().optional(),
});

type SetOrEditReminderDialogProps = {
open: boolean;
onClose: () => void;
reminder?: z.infer<typeof setReminderSchema> & { id: string };
};

export default function SetOrEditReminderDialog({
open,
onClose,
reminder,
}: SetOrEditReminderDialogProps) {
const navigation = useNavigation();
const disabled = isFormProcessing(navigation.state);

const pathname = useLocation().pathname;
const [searchParams, setSearchParams] = useSearchParams();

const redirectTo = `${pathname}${
searchParams.size > 0
? `?${searchParams.toString()}&success=true`
: "?success=true"
}`;

const zo = useZorm("SetOrEditReminder", setReminderSchema);

const isEdit = !!reminder;

useEffect(
function handleOnSuccess() {
if (searchParams.get("success") === "true") {
onClose && onClose();

setSearchParams((prev) => {
prev.delete("success");
return prev;
});
}
},
[onClose, searchParams, setSearchParams]
);

return (
<DialogPortal>
<Dialog
open={open}
onClose={onClose}
className="md:w-[800px]"
headerClassName="border-b"
title={
<div className="-mb-3 w-full pb-6">
<h3>Set Reminder</h3>
<p className="text-gray-600">
Notify you and / or others via email about this asset.
</p>
</div>
}
>
<Form
ref={zo.ref}
method="POST"
encType="multipart/form-data"
className="grid grid-cols-1 divide-x md:grid-cols-2"
>
<div className="px-6 py-4">
<input
type="hidden"
name="intent"
value={isEdit ? "edit-reminder" : "set-reminder"}
/>
<input type="hidden" name="redirectTo" value={redirectTo} />
{isEdit ? (
<input type="hidden" name="id" value={reminder.id} />
) : (
false
)}

<Input
defaultValue={reminder?.name ?? ""}
name={zo.fields.name()}
error={zo.errors.name()?.message}
label="Name"
disabled={disabled}
autoFocus
required
placeholder="Enter name of reminder"
className="mb-4"
/>

<div className="mb-4">
<Input
defaultValue={reminder?.message ?? ""}
name={zo.fields.message()}
error={zo.errors.message()?.message}
label="Message"
disabled={disabled}
autoFocus
required
placeholder="Enter description..."
inputType="textarea"
className="mb-2"
/>
<p className="text-gray-500">
This will show in the reminder mail that gets sent to selected
team member(s). Curious about the reminder mail?{" "}
<Link to="#" className="text-primary underline">
See a sample
</Link>
.
</p>
</div>

<div>
<Input
defaultValue={
reminder?.alertDateTime
? dateForDateTimeInputValue(
new Date(reminder.alertDateTime)
)
: undefined
}
type="datetime-local"
name={zo.fields.alertDateTime()}
error={zo.errors.alertDateTime()?.message}
label="Alert Date"
disabled={disabled}
autoFocus
required
placeholder="Enter description..."
className="mb-2"
/>
<p className="text-gray-500">
We will send the reminder at this date/time.
</p>
</div>
</div>
<div>
<Separator className="md:hidden" />
<p className="border-b p-3 font-medium">Select team member(s)</p>
<TeamMembersSelector
defaultValues={reminder?.teamMembers}
error={zo.errors.teamMembers()?.message}
/>
</div>
<div className="flex items-center justify-end gap-2 border-t p-4 md:col-span-2">
<Button
role="button"
variant="secondary"
disabled={disabled}
onClick={onClose}
>
Cancel
</Button>
<Button role="button" disabled={disabled}>
Confirm
</Button>
</div>
</Form>
</Dialog>
</DialogPortal>
);
}
Loading