Skip to content

Commit

Permalink
Merge pull request #409 from sinamics/tagflags
Browse files Browse the repository at this point in the history
Add flags to flowrule tags.
  • Loading branch information
sinamics authored May 15, 2024
2 parents cd9f216 + 1870523 commit e279344
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 99 deletions.
41 changes: 41 additions & 0 deletions src/components/networkByIdPage/flowRule/flagsAndTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { useFlagsAndTags } from "./useFlagsAndTags";
import TagComponent from "./tagComponent";

interface IProp {
organizationId: string;
nwid: string;
memberId: string;
central?: boolean;
}

const FlagsAndTags = ({ organizationId, nwid, memberId, central = false }: IProp) => {
const { tagsByName, tagFlags, handleEnumChange, handleFlagsCheckboxChange } =
useFlagsAndTags({
organizationId,
nwid,
memberId,
central,
});

if (!tagsByName || Object.keys(tagsByName).length === 0) {
return <p className="text-sm text-gray-500">None</p>;
}

return (
<div className="flex flex-wrap gap-2">
{Object.entries(tagsByName).map(([tagName, tagDetails]) => (
<TagComponent
key={tagName}
tagName={tagName}
tagDetails={tagDetails}
tagFlags={tagFlags}
handleEnumChange={handleEnumChange}
handleFlagsCheckboxChange={handleFlagsCheckboxChange}
/>
))}
</div>
);
};

export default FlagsAndTags;
88 changes: 88 additions & 0 deletions src/components/networkByIdPage/flowRule/tagComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from "react";
import { TagDetails } from "~/types/local/member";

interface TagComponentProps {
tagName: string;
tagDetails: TagDetails;
tagFlags: Record<number, number>;
handleEnumChange: (
e: React.ChangeEvent<HTMLSelectElement>,
tagDetails: TagDetails,
) => void;
handleFlagsCheckboxChange: (
e: React.ChangeEvent<HTMLInputElement>,
flagValue: number,
tagDetails: TagDetails,
) => void;
}

const TagComponent: React.FC<TagComponentProps> = ({
tagName,
tagDetails,
tagFlags,
handleEnumChange,
handleFlagsCheckboxChange,
}) => {
const tagValue = tagFlags[tagDetails.id] ?? 0;
const selectedOption =
Object.entries(tagDetails.enums).find(([, value]) => value === tagValue)?.[0] ??
"None";

return (
<div className="form-control rounded-md border w-full border-base-300 p-2">
<div>
<label className="label">
<span className="label-text">{tagName.toUpperCase()}</span>
</label>
<div className="flex gap-3">
<select
className="select select-bordered select-sm capitalize"
onChange={(e) => handleEnumChange(e, tagDetails)}
value={selectedOption}
>
<option value="None">None</option>
{Object.entries(tagDetails.enums).map(([option]) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<input
type="text"
placeholder="Type here"
readOnly
className="input input-sm input-bordered w-2/5"
value={
selectedOption in tagDetails.enums
? tagDetails.enums[selectedOption]
: tagValue || "None"
}
/>
</div>
</div>

<div>
<label className="label">
<span className="label-text">Flags:</span>
</label>
<div className="flex flex-wrap">
{Object.entries(tagDetails.flags)
.sort((a, b) => a[1] - b[1])
.map(([flagKey, flagValue]) => (
<label key={flagKey} className="label cursor-pointer space-x-2">
<input
type="checkbox"
className="checkbox checkbox-sm"
checked={(tagFlags[tagDetails.id] & flagValue) !== 0}
onChange={(e) => handleFlagsCheckboxChange(e, flagValue, tagDetails)}
/>
<span className="label-text">{flagKey}</span>
</label>
))}
</div>
</div>
</div>
);
};

export default TagComponent;
122 changes: 122 additions & 0 deletions src/components/networkByIdPage/flowRule/useFlagsAndTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import {
useTrpcApiErrorHandler,
useTrpcApiSuccessHandler,
} from "~/hooks/useTrpcApiHandler";
import { TagDetails } from "~/types/local/member";
import { api } from "~/utils/api";

interface IProp {
organizationId: string;
nwid: string;
memberId: string;
central?: boolean;
}

export const useFlagsAndTags = ({ organizationId, nwid, memberId, central }: IProp) => {
const { query } = useRouter();
const [tagFlags, setTagFlags] = useState<Record<number, number>>({});

const handleApiError = useTrpcApiErrorHandler();
const handleApiSuccess = useTrpcApiSuccessHandler();
const { data: memberById, refetch: refetchMemberById } =
api.networkMember.getMemberById.useQuery(
{
nwid,
id: memberId,
central,
},
{ enabled: !!query.id, networkMode: "online" },
);
const { data: networkById, refetch: refetchNetworkById } =
api.network.getNetworkById.useQuery(
{
nwid,
central,
},
{ enabled: !!query.id },
);

useEffect(() => {
const initialFlags: Record<number, number> = {};
if (memberById?.tags) {
for (const [tagId, flags] of memberById.tags) {
initialFlags[tagId] = flags;
}
}
setTagFlags(initialFlags);
}, [memberById?.tags]);

const { mutate: updateTags } = api.networkMember.Tags.useMutation({
onError: handleApiError,
onSuccess: handleApiSuccess({ actions: [refetchMemberById, refetchNetworkById] }),
});

const handleEnumChange = (
e: React.ChangeEvent<HTMLSelectElement>,
tagDetails: TagDetails,
) => {
const selectedOption = e.target.value;
const selectedValue = tagDetails.enums[selectedOption];
const tagId = tagDetails.id;

const newTagFlags = { ...tagFlags };
if (selectedOption === "None") {
delete newTagFlags[tagId];
} else {
newTagFlags[tagId] = selectedValue;
}

setTagFlags(newTagFlags);

const tags: [number, number][] = Object.entries(newTagFlags).map(([id, flags]) => [
Number(id),
flags,
]);
updateTags({
updateParams: {
tags,
},
organizationId,
memberId,
central,
nwid,
});
};

const handleFlagsCheckboxChange = (
e: React.ChangeEvent<HTMLInputElement>,
flagValue: number,
tagDetails: TagDetails,
) => {
const tagId = tagDetails.id;

const newTagFlags = { ...tagFlags };
if (e.target.checked) {
newTagFlags[tagId] |= flagValue; // Add the flag using bitwise OR
} else {
newTagFlags[tagId] &= ~flagValue; // Remove the flag using bitwise AND NOT
}

setTagFlags(newTagFlags);

const tags: [number, number][] = Object.entries(newTagFlags).map(([id, flags]) => [
Number(id),
flags,
]);
updateTags({
updateParams: {
tags,
},
organizationId,
memberId,
central,
nwid,
});
};

const tagsByName = networkById?.network?.tagsByName;

return { tagsByName, tagFlags, handleEnumChange, handleFlagsCheckboxChange };
};
103 changes: 8 additions & 95 deletions src/components/networkByIdPage/memberOptionsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ import { useState, useEffect } from "react";
import { type Prisma } from "@prisma/client";
import Anotation from "./anotation";
import { useTranslations } from "next-intl";
import {
type MemberEntity,
type CapabilitiesByName,
type TagDetails,
} from "~/types/local/member";
import { type TagsByName } from "~/types/local/network";
// import { useModalStore } from "~/utils/store";
import { type MemberEntity, type CapabilitiesByName } from "~/types/local/member";
import FlagsAndTags from "./flowRule/flagsAndTags";
import {
useTrpcApiErrorHandler,
useTrpcApiSuccessHandler,
Expand Down Expand Up @@ -92,11 +87,6 @@ export const MemberOptionsModal: React.FC<ModalContentProps> = ({
onSuccess: handleApiSuccess({ actions: [refetchNetworkById, refetchMemberById] }),
});

const { mutate: updateTags } = api.networkMember.Tags.useMutation({
onError: handleApiError,
onSuccess: handleApiSuccess({ actions: [refetchNetworkById] }),
});

// const stashMember = (id: string) => {
// stashUser({
// organizationId,
Expand Down Expand Up @@ -242,88 +232,6 @@ export const MemberOptionsModal: React.FC<ModalContentProps> = ({
);
};

const TagDropdowns: React.FC = (tagsByName: TagsByName) => {
const handleDropdownChange = (
e: React.ChangeEvent<HTMLSelectElement>,
tagDetails: TagDetails,
) => {
const selectedOption = e.target.value;
const selectedValue = tagDetails.enums[selectedOption];
const tagId = tagDetails.id;

// Create a Map from existing tags for easy lookup and update
const tagMap = new Map(memberById.tags);

if (selectedOption === "None") {
tagMap.delete(tagId); // Delete the entry if "None" is selected
} else {
// Update the value for this tagId in the map
tagMap.set(tagId, selectedValue);
}

// Convert back to the array of arrays format
const tags = Array.from(tagMap.entries());

updateTags({
updateParams: {
tags,
},
organizationId,
memberId,
central,
nwid,
});
};

if (!tagsByName || Object.keys(tagsByName).length === 0) {
return <p className="text-sm text-gray-500">None</p>;
}
// Create a Map from existing tags for easy lookup
const tagMap = new Map(memberById?.tags as [number, number][]);

return (
<div className="flex flex-wrap gap-2">
{Object.entries(tagsByName).map(([tagName, tagDetails]) => {
if (!tagDetails || typeof tagDetails !== "object" || !tagDetails.enums) {
return null;
}

// Find the value for this tag in memberById
const tagValue = tagMap.get(tagDetails.id);

// Find the corresponding option for this value
const selectedOption =
Object.entries(tagDetails.enums).find(
([_, value]) => value === tagValue,
)?.[0] ?? "None";

// console.log(selectedOption);
return (
<div
key={tagName}
className="form-control w-5/12 rounded-md border border-base-100 p-2"
>
<label className="label">
<span className="label-text">{tagName.toUpperCase()}</span>
</label>
<select
className="select select-bordered select-sm"
onChange={(e) => handleDropdownChange(e, tagDetails)}
value={selectedOption}
>
<option value="None">None</option>
{Object.entries(tagDetails.enums).map(([option]) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
);
})}
</div>
);
};
return (
<div>
{updateMemberLoading ? (
Expand Down Expand Up @@ -472,7 +380,12 @@ export const MemberOptionsModal: React.FC<ModalContentProps> = ({
<div className="grid grid-cols-4 items-start gap-4 py-3">
<div className="col-span-4">
<header>{t("networkById.memberOptionModal.tags.header")}</header>
{TagDropdowns(networkById?.network?.tagsByName)}
<FlagsAndTags
organizationId={organizationId}
nwid={nwid}
memberId={memberId}
central={central}
/>
</div>
</div>
{!central ? (
Expand Down
2 changes: 1 addition & 1 deletion src/types/central/network.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ interface CapabilitiesByName {
interface TagDetails {
default: number | null;
enums: Record<string, number>;
flags: Record<string, string>;
flags: Record<string, number>;
id: number;
}

Expand Down
Loading

0 comments on commit e279344

Please sign in to comment.