From a49aab363ade59e8ac7db74bcb72c82869881cc8 Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 19:56:42 +0200 Subject: [PATCH 1/8] fix: PBI 119509 - lookup link on same line --- Website/components/attributes/LookupAttribute.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Website/components/attributes/LookupAttribute.tsx b/Website/components/attributes/LookupAttribute.tsx index 54d53c4..f0fc246 100644 --- a/Website/components/attributes/LookupAttribute.tsx +++ b/Website/components/attributes/LookupAttribute.tsx @@ -6,9 +6,9 @@ export default function LookupAttribute({ attribute }: { attribute: LookupAttrib const { scrollToSection } = useDatamodelView(); - return <> + return

Lookup

-
+
{attribute.Targets .map(target => target.IsInSolution ?
- +
} \ No newline at end of file From 46324725d2fefa40a84120b0b52190ae4079d8ed Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 20:08:56 +0200 Subject: [PATCH 2/8] fix: PBI 119508 - additional standard fields --- Generator/MetadataExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Generator/MetadataExtensions.cs b/Generator/MetadataExtensions.cs index 03ec00a..3577e4d 100644 --- a/Generator/MetadataExtensions.cs +++ b/Generator/MetadataExtensions.cs @@ -58,11 +58,13 @@ private static bool StandardDescriptionHasChanged(this IEnumerable<(string Logic return EnglishDefaultFields.Concat(new[] { ("statuscode", "Status Reason", $"Reason for the status of the {entityDisplayName}"), ("statecode", "Status Reason", $"Status of the {entityDisplayName}"), + ("organizationid", "Organization", $"Unique identifier of the organization associated with the {entityDisplayName}."), }); default: return EnglishDefaultFields.Concat(new[] { ("statuscode", "Status Reason", $"Reason for the status of the {entityDisplayName}"), ("statecode", "Status Reason", $"Status of the {entityDisplayName}"), + ("organizationid", "Organization", $"Unique identifier of the organization associated with the {entityDisplayName}."), }); ; } } @@ -225,6 +227,7 @@ private static bool StandardDescriptionHasChanged(this IEnumerable<(string Logic ( "utcconversiontimezonecode", "Tidszonekode til UTC-konvertering", "Den tidszonekode, der var i brug ved oprettelse af posten." ), ( "utcconversiontimezonecode", "Tidszonekode for UTC-konvertering", "Den tidszonekode, der var i brug ved oprettelse af posten." ), ( "versionnumber", "Versionsnummer", "Versionsnummer" ), - ( "versionnumber", "Versionsnummer", "Versionsnummer for aktiviteten." ) + ( "versionnumber", "Versionsnummer", "Versionsnummer for aktiviteten." ), + ( "organizationid", "Organisations-id", "Entydigt id for organisationen" ), }; } From 30e40b33d995f6c0a8e0896b7790693b67bd5946 Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 20:12:01 +0200 Subject: [PATCH 3/8] fix: PBI 119510 - checkboxes for multi-select instead of radio buttons --- .../components/attributes/ChoiceAttribute.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Website/components/attributes/ChoiceAttribute.tsx b/Website/components/attributes/ChoiceAttribute.tsx index 59717db..e2d18db 100644 --- a/Website/components/attributes/ChoiceAttribute.tsx +++ b/Website/components/attributes/ChoiceAttribute.tsx @@ -1,6 +1,6 @@ import { ChoiceAttributeType } from "@/lib/Types" import { formatNumberSeperator } from "@/lib/utils" -import { CheckCircle, Circle } from "lucide-react" +import { CheckCircle, Circle, Square, CheckSquare } from "lucide-react" export default function ChoiceAttribute({ attribute }: { attribute: ChoiceAttributeType }) { return ( @@ -20,10 +20,20 @@ export default function ChoiceAttribute({ attribute }: { attribute: ChoiceAttrib
- {option.Value === attribute.DefaultValue ? ( - + {attribute.Type === "Multi" ? ( + // For multi-select, show checkboxes + option.Value === attribute.DefaultValue ? ( + + ) : ( + + ) ) : ( - + // For single-select, show radio buttons + option.Value === attribute.DefaultValue ? ( + + ) : ( + + ) )} {option.Name}
From f8cf3ac2d998059c259205a59c473488194afc5f Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 20:25:26 +0200 Subject: [PATCH 4/8] fix: PBI 119354 - Group search and minor navitem style changes --- .../datamodelview/SidebarDatamodelView.tsx | 102 ++++++++++++++++-- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/Website/components/datamodelview/SidebarDatamodelView.tsx b/Website/components/datamodelview/SidebarDatamodelView.tsx index 16f3bf5..fb5032d 100644 --- a/Website/components/datamodelview/SidebarDatamodelView.tsx +++ b/Website/components/datamodelview/SidebarDatamodelView.tsx @@ -5,8 +5,9 @@ import { useSidebarDispatch } from '@/contexts/SidebarContext'; import { cn } from "@/lib/utils"; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@radix-ui/react-collapsible"; import { Slot } from "@radix-ui/react-slot"; -import { ChevronDown, Puzzle } from "lucide-react"; +import { ChevronDown, Puzzle, Search, X } from "lucide-react"; import { useState, useEffect } from "react"; +import { Input } from "@/components/ui/input"; import { useDatamodelView, useDatamodelViewDispatch } from "@/contexts/DatamodelViewContext"; interface ISidebarDatamodelViewProps { @@ -24,10 +25,55 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => { const { currentSection, currentGroup, scrollToSection } = useDatamodelView(); const dataModelDispatch = useDatamodelViewDispatch(); + const [searchTerm, setSearchTerm] = useState(""); + const [expandedGroups, setExpandedGroups] = useState>(new Set()); + const setOpen = (state: boolean) => { dispatch({ type: "SET_OPEN", payload: state }) } + // Search functionality + const handleSearch = (term: string) => { + setSearchTerm(term); + if (term.trim()) { + const newExpandedGroups = new Set(); + Groups.forEach(group => { + const hasMatchingEntity = group.Entities.some(entity => + entity.SchemaName.toLowerCase().includes(term.toLowerCase()) || + entity.DisplayName.toLowerCase().includes(term.toLowerCase()) + ); + if (hasMatchingEntity) { + newExpandedGroups.add(group.Name); + } + }); + setExpandedGroups(newExpandedGroups); + } else { + setExpandedGroups(new Set()); + } + }; + + const clearSearch = () => { + setSearchTerm(""); + setExpandedGroups(new Set()); + }; + + const isEntityMatch = (entity: any) => { + if (!searchTerm.trim()) return false; + return entity.SchemaName.toLowerCase().includes(searchTerm.toLowerCase()) || + entity.DisplayName.toLowerCase().includes(searchTerm.toLowerCase()); + }; + + const highlightText = (text: string, searchTerm: string) => { + if (!searchTerm.trim()) return text; + const regex = new RegExp(`(${searchTerm})`, 'gi'); + const parts = text.split(regex); + return parts.map((part, index) => + regex.test(part) ? + {part} : + part + ); + }; + const handleGroupClick = (groupName: string) => { dataModelDispatch({ type: "SET_CURRENT_GROUP", payload: groupName }); }; @@ -37,27 +83,33 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => { scrollToSection(sectionId); } if (isTouch) { setOpen(false); } + clearSearch(); }; const NavItem = ({ group }: INavItemProps) => { const isCurrentGroup = currentGroup?.toLowerCase() === group.Name.toLowerCase(); + const shouldExpand = expandedGroups.has(group.Name); const [isExpanded, setIsExpanded] = useState(false) useEffect(() => { - setIsExpanded(isCurrentGroup) - }, [isCurrentGroup]) + if (searchTerm.trim()) { + setIsExpanded(shouldExpand); + } else { + setIsExpanded(isCurrentGroup); + } + }, [isCurrentGroup, shouldExpand, searchTerm]) return (
@@ -69,7 +121,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => { )} data-state={isExpanded ? 'open' : 'closed'} > - {group.Name} + {group.Name}

{group.Entities.length}

handleGroupClick(group.Name)}/> @@ -78,18 +130,28 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
{group.Entities.map(entity => { const isCurrentSection = currentSection?.toLowerCase() === entity.SchemaName.toLowerCase() + const isMatch = isEntityMatch(entity); + + // If searching and this entity doesn't match, don't render it + if (searchTerm.trim() && !isMatch) { + return null; + } + return ( ) })} @@ -102,7 +164,29 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => { return (
-
+ {/* Search Bar */} +
+
+ + handleSearch(e.target.value)} + className="pl-8 pr-8 h-8 text-xs" + /> + {searchTerm && ( + + )} +
+
+ +
{ Groups.map((group) => From dc758dcdc56b69cf40b3fa2ff054fe95625213ee Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 20:40:09 +0200 Subject: [PATCH 5/8] chore: PBI 119269 - smaller text for mobile --- .../attributes/BooleanAttribute.tsx | 32 ++++++++------- .../components/attributes/ChoiceAttribute.tsx | 30 ++++++++------ .../attributes/DateTimeAttribute.tsx | 2 +- .../attributes/DecimalAttribute.tsx | 4 +- .../components/attributes/FileAttribute.tsx | 2 +- .../attributes/GenericAttribute.tsx | 2 +- .../attributes/IntegerAttribute.tsx | 2 +- .../components/attributes/LookupAttribute.tsx | 16 ++++---- .../components/attributes/StatusAttribute.tsx | 12 +++--- .../components/attributes/StringAttribute.tsx | 2 +- .../components/datamodelview/Attributes.tsx | 38 +++++++++--------- Website/components/datamodelview/Keys.tsx | 24 +++++------ .../datamodelview/Relationships.tsx | 40 +++++++++---------- 13 files changed, 107 insertions(+), 99 deletions(-) diff --git a/Website/components/attributes/BooleanAttribute.tsx b/Website/components/attributes/BooleanAttribute.tsx index 43cace7..6570bb1 100644 --- a/Website/components/attributes/BooleanAttribute.tsx +++ b/Website/components/attributes/BooleanAttribute.tsx @@ -1,45 +1,49 @@ +import { useIsMobile } from "@/hooks/use-mobile"; import { BooleanAttributeType } from "@/lib/Types" import { CheckCircle, Circle } from "lucide-react" export default function BooleanAttribute({ attribute }: { attribute: BooleanAttributeType }) { + + const isMobile = useIsMobile(); + return (
- Boolean - {attribute.DefaultValue !== null && ( - - + Boolean + {attribute.DefaultValue !== null && !isMobile && ( + + Default: {attribute.DefaultValue === true ? attribute.TrueLabel : attribute.FalseLabel} )}
-
+
{attribute.DefaultValue === true ? ( - + ) : ( - + )} - {attribute.TrueLabel} + {attribute.TrueLabel}
- + True
-
+
{attribute.DefaultValue === false ? ( - + ) : ( - + )} - {attribute.FalseLabel} + {attribute.FalseLabel}
- + False
diff --git a/Website/components/attributes/ChoiceAttribute.tsx b/Website/components/attributes/ChoiceAttribute.tsx index e2d18db..aa087fb 100644 --- a/Website/components/attributes/ChoiceAttribute.tsx +++ b/Website/components/attributes/ChoiceAttribute.tsx @@ -1,15 +1,19 @@ +import { useIsMobile } from "@/hooks/use-mobile" import { ChoiceAttributeType } from "@/lib/Types" import { formatNumberSeperator } from "@/lib/utils" import { CheckCircle, Circle, Square, CheckSquare } from "lucide-react" export default function ChoiceAttribute({ attribute }: { attribute: ChoiceAttributeType }) { + + const isMobile = useIsMobile(); + return (
- {attribute.Type}-select - {attribute.DefaultValue !== null && attribute.DefaultValue !== -1 && ( - - + {attribute.Type}-select + {attribute.DefaultValue !== null && attribute.DefaultValue !== -1 && !isMobile && ( + + Default: {attribute.Options.find(o => o.Value === attribute.DefaultValue)?.Name} )} @@ -17,42 +21,42 @@ export default function ChoiceAttribute({ attribute }: { attribute: ChoiceAttrib
{attribute.Options.map(option => (
-
+
{attribute.Type === "Multi" ? ( // For multi-select, show checkboxes option.Value === attribute.DefaultValue ? ( - + ) : ( - + ) ) : ( // For single-select, show radio buttons option.Value === attribute.DefaultValue ? ( - + ) : ( - + ) )} - {option.Name} + {option.Name}
{option.Color && (
)}
- + {formatNumberSeperator(option.Value)}
{option.Description && ( -
+
{option.Description}
)} diff --git a/Website/components/attributes/DateTimeAttribute.tsx b/Website/components/attributes/DateTimeAttribute.tsx index b5655a2..6d6b69c 100644 --- a/Website/components/attributes/DateTimeAttribute.tsx +++ b/Website/components/attributes/DateTimeAttribute.tsx @@ -1,5 +1,5 @@ import { DateTimeAttributeType } from "@/lib/Types"; export default function DateTimeAttribute({ attribute } : { attribute: DateTimeAttributeType }) { - return <>{attribute.Format} - {attribute.Behavior} + return <>{attribute.Format} - {attribute.Behavior} } \ No newline at end of file diff --git a/Website/components/attributes/DecimalAttribute.tsx b/Website/components/attributes/DecimalAttribute.tsx index 2a9be40..cd73150 100644 --- a/Website/components/attributes/DecimalAttribute.tsx +++ b/Website/components/attributes/DecimalAttribute.tsx @@ -8,8 +8,8 @@ export default function MoneyAttribute({ attribute }: { attribute: DecimalAttrib : FormatDecimal return <> -

{attribute.Type} ({formatNumber(attribute.MinValue)} to {formatNumber(attribute.MaxValue)})

-

Precision: {attribute.Precision}

+

{attribute.Type} ({formatNumber(attribute.MinValue)} to {formatNumber(attribute.MaxValue)})

+

Precision: {attribute.Precision}

} diff --git a/Website/components/attributes/FileAttribute.tsx b/Website/components/attributes/FileAttribute.tsx index 2afee50..39ba278 100644 --- a/Website/components/attributes/FileAttribute.tsx +++ b/Website/components/attributes/FileAttribute.tsx @@ -2,5 +2,5 @@ import { FileAttributeType } from "@/lib/Types"; import { formatNumberSeperator } from "@/lib/utils"; export default function FileAttribute({ attribute } : { attribute: FileAttributeType }) { - return <>File (Max {formatNumberSeperator(attribute.MaxSize)}KB) + return <>File (Max {formatNumberSeperator(attribute.MaxSize)}KB) } \ No newline at end of file diff --git a/Website/components/attributes/GenericAttribute.tsx b/Website/components/attributes/GenericAttribute.tsx index b94090e..d0e0db1 100644 --- a/Website/components/attributes/GenericAttribute.tsx +++ b/Website/components/attributes/GenericAttribute.tsx @@ -1,5 +1,5 @@ import { GenericAttributeType } from "@/lib/Types"; export default function GenericAttribute({ attribute } : { attribute: GenericAttributeType }) { - return {attribute.Type} + return {attribute.Type} } \ No newline at end of file diff --git a/Website/components/attributes/IntegerAttribute.tsx b/Website/components/attributes/IntegerAttribute.tsx index a5a8ea5..8c72f40 100644 --- a/Website/components/attributes/IntegerAttribute.tsx +++ b/Website/components/attributes/IntegerAttribute.tsx @@ -2,7 +2,7 @@ import { IntegerAttributeType } from "@/lib/Types" import { formatNumberSeperator } from "@/lib/utils" export default function IntegerAttribute({ attribute } : { attribute: IntegerAttributeType }) { - return <>{attribute.Format} ({FormatNumber(attribute.MinValue)} to {FormatNumber(attribute.MaxValue)}) + return <>{attribute.Format} ({FormatNumber(attribute.MinValue)} to {FormatNumber(attribute.MaxValue)}) } function FormatNumber(number: number) { diff --git a/Website/components/attributes/LookupAttribute.tsx b/Website/components/attributes/LookupAttribute.tsx index f0fc246..4ac5f5d 100644 --- a/Website/components/attributes/LookupAttribute.tsx +++ b/Website/components/attributes/LookupAttribute.tsx @@ -6,25 +6,25 @@ export default function LookupAttribute({ attribute }: { attribute: LookupAttrib const { scrollToSection } = useDatamodelView(); - return
-

Lookup

+ return
+

Lookup

{attribute.Targets .map(target => target.IsInSolution ? :
- - {target.Name} + + {target.Name}
)}
diff --git a/Website/components/attributes/StatusAttribute.tsx b/Website/components/attributes/StatusAttribute.tsx index 889a5a0..41974bb 100644 --- a/Website/components/attributes/StatusAttribute.tsx +++ b/Website/components/attributes/StatusAttribute.tsx @@ -11,16 +11,16 @@ export default function StatusAttribute({ attribute }: { attribute: StatusAttrib }, {} as Record) return ( -
- State/Status +
+ State/Status {Object.entries(groupedOptions).map(([state, options]) => (
- {state} -
+ {state} +
{options.map(option => (
- {option.Name} - {formatNumberSeperator(option.Value)} + {option.Name} + {formatNumberSeperator(option.Value)}
))}
diff --git a/Website/components/attributes/StringAttribute.tsx b/Website/components/attributes/StringAttribute.tsx index a25b74b..2c2e461 100644 --- a/Website/components/attributes/StringAttribute.tsx +++ b/Website/components/attributes/StringAttribute.tsx @@ -4,5 +4,5 @@ import { StringAttributeType } from "@/lib/Types"; import { formatNumberSeperator } from "@/lib/utils"; export default function StringAttribute({ attribute } : { attribute: StringAttributeType }) { - return <>Text ({formatNumberSeperator(attribute.MaxLength)}){attribute.Format !== "Text" ? ` - ${attribute.Format}` : ""}; + return <>Text ({formatNumberSeperator(attribute.MaxLength)}){attribute.Format !== "Text" ? ` - ${attribute.Format}` : ""}; } \ No newline at end of file diff --git a/Website/components/datamodelview/Attributes.tsx b/Website/components/datamodelview/Attributes.tsx index e489037..6e18c35 100644 --- a/Website/components/datamodelview/Attributes.tsx +++ b/Website/components/datamodelview/Attributes.tsx @@ -125,23 +125,23 @@ export const Attributes = ({ entity }: IAttributeProps) => { ] return <> -
+
- + setSearchQuery(e.target.value)} - className="pl-8" + className="pl-6 h-8 text-xs md:pl-8 md:h-10 md:text-sm" />
setSearchQuery(e.target.value)} - className="pl-8" + className="pl-6 h-8 text-xs md:pl-8 md:h-10 md:text-sm" />
{searchQuery && ( @@ -98,10 +98,10 @@ function Keys({ entity }: { entity: EntityType }) { variant="ghost" size="icon" onClick={() => setSearchQuery("")} - className="h-10 w-10 text-gray-500 hover:text-gray-700" + className="h-8 w-8 text-gray-500 hover:text-gray-700 md:h-10 md:w-10" title="Clear search" > - + )}
@@ -128,7 +128,7 @@ function Keys({ entity }: { entity: EntityType }) { handleSort('name')} >
@@ -137,7 +137,7 @@ function Keys({ entity }: { entity: EntityType }) {
handleSort('logicalName')} >
@@ -146,7 +146,7 @@ function Keys({ entity }: { entity: EntityType }) {
handleSort('attributes')} >
@@ -164,14 +164,14 @@ function Keys({ entity }: { entity: EntityType }) { index % 2 === 0 ? 'bg-white' : 'bg-gray-50/50' }`} > - {key.Name} - {key.LogicalName} - + {key.Name} + {key.LogicalName} +
{key.KeyAttributes.map((attr, i) => ( {attr} diff --git a/Website/components/datamodelview/Relationships.tsx b/Website/components/datamodelview/Relationships.tsx index 499c1c0..8b48d73 100644 --- a/Website/components/datamodelview/Relationships.tsx +++ b/Website/components/datamodelview/Relationships.tsx @@ -115,23 +115,23 @@ export const Relationships = ({ entity }: IRelationshipsProps) => { ] return <> -
+
- + setSearchQuery(e.target.value)} - className="pl-8" + className="pl-6 h-8 text-xs md:pl-8 md:h-10 md:text-sm" />
handleSearch(e.target.value)} className="pl-8 pr-8 h-8 text-xs" From 60de427f580a3b5133f300a758cb95637ba7ade1 Mon Sep 17 00:00:00 2001 From: boer Date: Fri, 11 Jul 2025 20:51:16 +0200 Subject: [PATCH 8/8] chore: ESLint error --- Website/components/datamodelview/SidebarDatamodelView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Website/components/datamodelview/SidebarDatamodelView.tsx b/Website/components/datamodelview/SidebarDatamodelView.tsx index fb5032d..0d0d0ad 100644 --- a/Website/components/datamodelview/SidebarDatamodelView.tsx +++ b/Website/components/datamodelview/SidebarDatamodelView.tsx @@ -1,4 +1,4 @@ -import { GroupType } from "@/lib/Types"; +import { EntityType, GroupType } from "@/lib/Types"; import { Groups } from "../../generated/Data" import { useTouch } from '../ui/hybridtooltop'; import { useSidebarDispatch } from '@/contexts/SidebarContext'; @@ -57,7 +57,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => { setExpandedGroups(new Set()); }; - const isEntityMatch = (entity: any) => { + const isEntityMatch = (entity: EntityType) => { if (!searchTerm.trim()) return false; return entity.SchemaName.toLowerCase().includes(searchTerm.toLowerCase()) || entity.DisplayName.toLowerCase().includes(searchTerm.toLowerCase());