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: Sidebar Menu Items Search Functionality #11

Merged
merged 3 commits into from
Nov 4, 2024

Conversation

mehedijaman
Copy link
Contributor

@mehedijaman mehedijaman commented Oct 30, 2024

Implemented a search feature within the sidebar menu to enhance user experience.
image

Added count cards for Users, Roles, and Permissions in the Dashboard.
image

Finally the dashboard looks like this -
image

Copy link
Collaborator

@daniel-cintra daniel-cintra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi!

Thanks for your time and effort in this PR. Please check the comments in the Review.

Comment on lines 15 to 31
<div
class="shadow-xs flex items-center rounded-lg border border-skin-neutral-4 bg-skin-neutral-2 p-4 hover:cursor-pointer hover:bg-skin-neutral-1"
@click="$inertia.visit(route('user.index'))"
v-if="can('Acl')"
>
<div
class="mr-4 rounded-full bg-green-100 px-3 py-2 text-green-500 dark:bg-green-500 dark:text-green-100"
>
<i class="ri-user-fill text-2xl"></i>
</div>
<div>
<p class="mb-2 text-sm font-medium">Users</p>
<p class="text-lg font-semibold">
{{ props.count['users'] }}
</p>
</div>
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please extract to a dedicated DashboardCard.vue Component. Save it in stubs/resources/js/Pages/Dashboard/Components/DashboardCard.vue. Use this same component for all the 3 cards...

Comment on lines 47 to 66
const searchTerm = ref('')

// Computed property to filter items based on search term
const filteredItems = computed(() => {
if (!searchTerm.value) return props.items
const searchFilter = (item) => {
// Convert search term to lowercase for case-insensitive matching
const term = searchTerm.value.toLowerCase()

// Check if the label or any child item contains the search term
const matches = (i) =>
i.label?.toLowerCase().includes(term) ||
(i.children && i.children.some(matches)) // Recursively check children

return matches(item)
}

// Filter items recursively
return props.items.filter(searchFilter)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of a search can be useful. I made some tests and updated it to cover other scenarios so for tests purposes:

resources/js/Configs/menu.js

export default {
    // main navigation - side menu
    items: [
        {
            label: 'Dashboard',
            permission: 'Dashboard',
            icon: 'ri-dashboard-line',
            link: route('dashboard.index')
        },

        {
            label: 'Banners',
            permission: 'Main Menu: Banners',
            children: [
                {
                    label: 'Sections',
                    permission: 'Main Menu: Banners: Sections - List',
                    icon: 'ri-landscape-line',
                    link: route('dashboard.index')
                }
            ]
        },

        {
            label: 'Page',
            permission: 'Page',
            children: [
                {
                    label: 'Pages',
                    permission: 'Page: Page - List',
                    icon: 'ri-draft-line',
                    link: route('dashboard.index')
                }
            ]
        },

        {
            label: 'Blog',
            permission: 'Blog',
            children: [
                {
                    label: 'Posts',
                    permission: 'Blog: Post - List',
                    icon: 'ri-draft-line',
                    link: route('dashboard.index')
                },
                {
                    label: 'Categories',
                    permission: 'Blog: Category - List',
                    icon: 'ri-folders-line',
                    link: route('dashboard.index')
                },
                {
                    label: 'Tags',
                    permission: 'Blog: Tag - List',
                    icon: 'ri-price-tag-3-line',
                    link: route('dashboard.index')
                },
                {
                    label: 'Authors',
                    permission: 'Blog: Author - List',
                    icon: 'ri-team-line',
                    link: route('dashboard.index')
                }
            ]
        },

        {
            label: 'Newsletter',
            permission: 'Newsletter',
            children: [
                {
                    label: 'Newsletter List',
                    permission: 'Newsletter: List',
                    icon: 'ri-mail-line',
                    link: route('dashboard.index')
                }
            ]
        },

        {
            label: 'Access Control List',
            permission: 'Acl',
            children: [
                {
                    label: 'Users',
                    permission: 'Acl: User - List',
                    icon: 'ri-user-line',
                    link: route('user.index')
                },
                {
                    label: 'Permissions',
                    permission: 'Acl: Permission - List',
                    icon: 'ri-shield-keyhole-line',
                    link: route('aclPermission.index')
                },
                {
                    label: 'Roles',
                    permission: 'Acl: Role - List',
                    icon: 'ri-account-box-line',
                    link: route('aclRole.index')
                }
            ]
        }
    ]
}

Updated search:

import { ref, computed } from 'vue'

const props = defineProps({
    items: {
        type: Array,
        default: () => []
    }
})

const searchTerm = ref('')

// Computed property to filter items based on search term
const filteredItems = computed(() => {
    if (!searchTerm.value) return props.items
    const term = searchTerm.value.toLowerCase()

    // Recursive function to filter items and their children
    const filterItems = (items) => {
        return items.reduce((acc, item) => {
            const isParentMatch = item.label.toLowerCase().includes(term)
            let filteredChildren = []

            if (item.children) {
                if (isParentMatch) {
                    // If parent matches, include all children
                    filteredChildren = item.children
                } else {
                    // Else, filter children based on search term
                    filteredChildren = filterItems(item.children)
                }
            }

            // Include the item if the parent matches or any of its children match
            if (
                isParentMatch ||
                (filteredChildren && filteredChildren.length)
            ) {
                acc.push({
                    ...item,
                    // If parent matches, include all children; else, include filtered children
                    children: isParentMatch ? item.children : filteredChildren
                })
            }

            return acc
        }, [])
    }

    return filterItems(props.items)
})

Now it will return the expected values for a larger amount of scenarios like:
"Access Control List" => It's a parent node. Return the parent and all childs.
"Posts" => It's a child node. Returns the parent label "Blog" and the searched term "Posts".

Please update to use this concept...

Fix: Created dedicated component for dashboard card
@mehedijaman
Copy link
Contributor Author

Hello, Thank you for your time to review my PR. I have updated code according to suggestions given above. Please check out.

Thanks.

@daniel-cintra
Copy link
Collaborator

Thank you @mehedijaman !

@daniel-cintra daniel-cintra merged commit ee17911 into ModularThink:main Nov 4, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants