Skip to content

Commit

Permalink
merged feat-add-asset-to-booking-by-scan-v2 and fixed conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
DonKoko committed Sep 6, 2024
2 parents b2e222f + 19f4491 commit 9c82188
Show file tree
Hide file tree
Showing 69 changed files with 4,341 additions and 4,666 deletions.
2 changes: 1 addition & 1 deletion app/atoms/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type NotificationIcon = {
export interface NotificationType {
open: boolean;
title: string;
message: string;
message?: string | null;
icon: NotificationIcon;
time?: number;
senderId: User["id"] | null;
Expand Down
114 changes: 114 additions & 0 deletions app/atoms/qr-scanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { atom } from "jotai";
import type { AssetWithBooking } from "~/routes/_layout+/bookings.$bookingId.add-assets";
import type { KitForBooking } from "~/routes/_layout+/bookings.$bookingId.add-kits";

export type ScanListItems = {
[key: string]: ScanListItem;
};

export type ScanListItem =
| {
data?: AssetWithBooking | KitForBooking;
error?: string;
type?: "asset" | "kit";
}
| undefined;

/***********************
* Scanned QR Id Atom *
*
* The data is structured in a object where:
* - key: qrId
* - value: asset
*
***********************/

export const scannedItemsAtom = atom<ScanListItems>({});

/** Get an array of the scanned items ids */
export const scannedItemsIdsAtom = atom((get) =>
Object.values(get(scannedItemsAtom)).map((item) => item?.data?.id)
);

// Add item to object with value `undefined` (just receives the key)
export const addScannedItemAtom = atom(
null,
(get, set, qrId: string, error?: string) => {
const currentItems = get(scannedItemsAtom);
if (!currentItems[qrId]) {
/** Set can optionally receive error. If it does, add it to the item.
* This is used for errors that are related to the QR code itself, not the item.
*/
set(scannedItemsAtom, {
[qrId]: error
? {
error: error,
}
: undefined, // Add the new entry at the start
...currentItems, // Spread the rest of the existing items
});
}
}
);

// Update item based on key
export const updateScannedItemAtom = atom(
null,
(get, set, { qrId, item }: { qrId: string; item: ScanListItem }) => {
const currentItems = get(scannedItemsAtom);

// Check if the item already exists; if it does, skip the update
if (!item || currentItems[qrId]) {
return; // Skip the update if the item is already present
}

if ((item && item?.data && item?.type) || item?.error) {
set(scannedItemsAtom, {
...currentItems,
[qrId]: item,
});
}
}
);

// Remove item based on key
export const removeScannedItemAtom = atom(null, (get, set, qrId: string) => {
const currentItems = get(scannedItemsAtom);
const { [qrId]: _, ...rest } = currentItems; // Removes the key
set(scannedItemsAtom, rest);
});

// Remove multiple items based on key array
export const removeMultipleScannedItemsAtom = atom(
null,
(get, set, qrIds: string[]) => {
const currentItems = get(scannedItemsAtom);
const updatedItems = { ...currentItems };
qrIds.forEach((qrId) => {
delete updatedItems[qrId];
});
set(scannedItemsAtom, updatedItems);
}
);

// Remove items based on asset id
export const removeScannedItemsByAssetIdAtom = atom(
null,
(get, set, ids: string[]) => {
const currentItems = get(scannedItemsAtom);
const updatedItems = { ...currentItems };
Object.entries(currentItems).forEach(([qrId, item]) => {
if (item?.data?.id && ids.includes(item?.data?.id)) {
delete updatedItems[qrId];
}
});
set(scannedItemsAtom, updatedItems);
}
);

// Clear all items
export const clearScannedItemsAtom = atom(null, (_get, set) => {
set(scannedItemsAtom, {}); // Resets the atom to an empty object
});

/*******************************/
33 changes: 28 additions & 5 deletions app/components/assets/custom-fields-inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from "../forms/select";
import { Switch } from "../forms/switch";
import { SearchIcon } from "../icons/library";
import { MarkdownEditor } from "../markdown/markdown-editor";
import { Button } from "../shared/button";

export default function AssetCustomFields({
Expand Down Expand Up @@ -58,7 +59,7 @@ export default function AssetCustomFields({
const getCustomFieldVal = (id: string) => {
const value = customFieldsValues?.find((cfv) => cfv.customFieldId === id)
?.value;
return value ? getCustomFieldDisplayValue(value, hints) : "";
return value ? (getCustomFieldDisplayValue(value, hints) as string) : "";
};

const fieldTypeToCompMap: {
Expand Down Expand Up @@ -171,6 +172,29 @@ export default function AssetCustomFields({
</>
);
},
MULTILINE_TEXT: (field) => {
const value = customFieldsValues?.find(
(cfv) => cfv.customFieldId === field.id
)?.value?.raw;

const error = zo.errors[`cf-${field.id}`]()?.message;

return (
<>
<MarkdownEditor
name={`cf-${field.id}`}
label={field.name}
defaultValue={value ? String(value) : ""}
placeholder={field.helpText ?? field.name}
disabled={disabled}
maxLength={5000}
/>
{error ? (
<p className="mt-1 text-sm text-error-500">{error}</p>
) : null}
</>
);
},
};

return (
Expand All @@ -189,7 +213,9 @@ export default function AssetCustomFields({
const value = customFieldsValues?.find(
(cfv) => cfv.customFieldId === field.id
)?.value;
const displayVal = value ? getCustomFieldDisplayValue(value) : "";
const displayVal = value
? (getCustomFieldDisplayValue(value) as string)
: "";
return (
<FormRow
key={field.id + index}
Expand All @@ -204,9 +230,6 @@ export default function AssetCustomFields({
<Input
hideLabel
placeholder={field.helpText || undefined}
inputType={
field.type === "MULTILINE_TEXT" ? "textarea" : "input"
}
type={field.type.toLowerCase()}
label={field.name}
name={`cf-${field.id}`}
Expand Down
32 changes: 19 additions & 13 deletions app/components/assets/notes/new.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import type { ChangeEvent, FocusEvent } from "react";
import { useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import type { FetcherWithComponents } from "@remix-run/react";
import { useParams } from "@remix-run/react";
import { atom, useAtom } from "jotai";
import { useZorm } from "react-zorm";
import { z } from "zod";
import Input from "~/components/forms/input";
import {
MarkdownEditor,
clearMarkdownAtom,
} from "~/components/markdown/markdown-editor";
import { MarkdownEditor } from "~/components/markdown/markdown-editor";
import { Button } from "~/components/shared/button";

export const NewNoteSchema = z.object({
Expand All @@ -29,9 +26,16 @@ export const NewNote = ({
const [isEditing, setIsEditing] = useAtom(isEditingAtom);
const editorRef = useRef<HTMLTextAreaElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const [, clearMarkdown] = useAtom(clearMarkdownAtom);
const isDone = fetcher.state === "idle" && fetcher.data != null;

/** Controls whether actions are disabled */
const [disabled, setDisabled] = useState<boolean>(false);

function handleSubmit() {
/** Disabled the input and buttons while form is submitting */
setDisabled(true);
}

const handelBlur = (
e: ChangeEvent<HTMLTextAreaElement> & FocusEvent<HTMLTextAreaElement>
) => {
Expand Down Expand Up @@ -65,23 +69,25 @@ export const NewNote = ({
[fetcher]
);

/** Focus the input when we are in edit mode */
useEffect(() => {
if (isEditing) {
editorRef?.current?.focus();
}
}, [isEditing]);

/** When submitting is done, set isEditing to false to close the editor */
useEffect(() => {
clearMarkdown();
}, [isDone, clearMarkdown]);
setIsEditing(false);
}, [isDone, setIsEditing]);

return (
<div ref={wrapperRef}>
<fetcher.Form
action={`/assets/${params.assetId}/note`}
method="post"
ref={zo.ref}
onSubmit={clearMarkdown}
onSubmit={handleSubmit}
>
{isEditing ? (
<div className="relative flex flex-col pb-12 xl:pb-0">
Expand All @@ -90,25 +96,25 @@ export const NewNote = ({
variant="secondary"
size="sm"
onClick={() => setIsEditing(false)}
disabled={disabled}
>
Cancel
</Button>
<Button type="submit" size="sm" className="">
<Button type="submit" size="sm" className="" disabled={disabled}>
Create note
</Button>
</div>
<MarkdownEditor
label={"note"}
defaultValue={""}
name={zo.fields.content()}
placeholder={"Leave a note"}
// @ts-ignore
placeholder="Leave a note"
rows={4}
ref={editorRef}
className="rounded-b-none"
onBlur={handelBlur}
onKeyDown={handleKeyDown}
maxLength={100000}
disabled={disabled}
/>
</div>
) : (
Expand Down
2 changes: 1 addition & 1 deletion app/components/booking/asset-row-actions-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const AssetRowActionsDropdown = ({ asset, fullWidth }: Props) => (
<DropdownMenuTrigger
className={tw("asset-actions", fullWidth ? "w-full" : "")}
>
<span className="flex items-center gap-2">
<span className="flex size-6 items-center justify-center gap-2 text-center">
<VerticalDotsIcon />
</span>
</DropdownMenuTrigger>
Expand Down
14 changes: 14 additions & 0 deletions app/components/booking/availability-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,29 @@ export function AvailabilityLabel({
isCheckedOut,
showKitStatus,
isAddedThroughKit,
isAlreadyAdded,
}: {
asset: AssetWithBooking;
isCheckedOut: boolean;
showKitStatus?: boolean;
isAddedThroughKit?: boolean;
isAlreadyAdded?: boolean;
}) {
const isPartOfKit = !!asset.kitId;

const { booking } = useLoaderData<{ booking: Booking }>();

/** User scanned the asset and it is already in booking */
if (isAlreadyAdded) {
return (
<AvailabilityBadge
badgeText="Already added to this booking"
tooltipTitle="Asset is part of booking"
tooltipContent="This asset is already added to the current booking."
/>
);
}

/**
* Marked as not allowed for booking
*/
Expand Down
Loading

0 comments on commit 9c82188

Please sign in to comment.