DHIS2 custom app

Hello team,

I am working on querying user data within DHIS2 using React. I was able to successfully fetch the user’s First Name, Username, Last Login, and Email. However, when it comes to fetching the Organization Unit, I am receiving empty values (i.e., ,,).

Could you help me identify what might be causing this issue? Below is the relevant code:
import React, { useState } from ‘react’;
import ‘./UserTable.css’;
import { useDataQuery } from ‘@dhis2/app-runtime’;

// Define the query to fetch necessary user data
const myQuery = {
results: {
resource: ‘users’,
params: {
pageSize: 2500, // Fetch 2500 users per request (adjust according to your system’s limit)
fields: [
‘id’,
‘firstName’,
‘email’,
‘username’,
‘displayName’,
‘lastLogin’,
‘organisationUnits’
],
},
},
};

const UserTable = () => {
const { loading, error, data } = useDataQuery(myQuery);

const [currentPage, setCurrentPage] = useState(1);
const [usersPerPage] = useState(20); // Number of users per page
const [searchQuery, setSearchQuery] = useState('');
const [selectedUser, setSelectedUser] = useState(null);  // State for selected user
const [isEditing, setIsEditing] = useState(false);  // State to toggle edit mode
const [editedUser, setEditedUser] = useState(null);  // State for editing user

if (error) {
    return <span>ERROR: {error.message}</span>;
}

if (loading) {
    return <span>Loading...</span>;
}

// Extract users from the API response
const users = data.results.users || [];

// Filter users based on search query
const filteredUsers = users.filter((user) => {
    const query = searchQuery.toLowerCase();
    return (
        (user.displayName && user.displayName.toLowerCase().includes(query)) ||
        (user.firstName && user.firstName.toLowerCase().includes(query)) ||
        (user.email && user.email.toLowerCase().includes(query)) ||
        (user.username && user.username.toLowerCase().includes(query))
    );
});

// Pagination calculation
const totalUsers = filteredUsers.length;
const totalPages = Math.ceil(totalUsers / usersPerPage);
const indexOfLastUser = currentPage * usersPerPage;
const indexOfFirstUser = indexOfLastUser - usersPerPage;
const currentUsers = filteredUsers.slice(indexOfFirstUser, indexOfLastUser);

// Handle page change
const handlePageChange = (pageNumber) => {
    setCurrentPage(pageNumber);
};

// Handle search input change
const handleSearchChange = (event) => {
    setSearchQuery(event.target.value);
};

// Handle user selection
const handleSelectUser = (user) => {
    setSelectedUser(user);
    setEditedUser(user);  // Set the selected user as the one to be edited
    setIsEditing(false);   // By default, don't start editing
};

// Handle edit toggle
const handleEditToggle = () => {
    setIsEditing(!isEditing);
};

// Handle change in edited fields
const handleInputChange = (event) => {
    const { name, value } = event.target;
    setEditedUser((prev) => ({
        ...prev,
        [name]: value,
    }));
};

// Convert data to CSV
const convertToCSV = (data) => {
    const header = ['Display Name', 'First Name', 'Username', 'Email', 'Last Login','orgUnit','Organisation Units'];
    const rows = data.map((user) => [
        user.displayName || 'N/A',
        user.firstName || 'N/A',
        user.username || 'N/A',
        user.email || 'N/A',
        user.lastLogin || 'N/A',
        user.organisationUnits?.map(unit => unit.name).join(', ') || 'N/A',
    ]);
    const csvContent = [
        header.join(','), // Add header row
        ...rows.map(row => row.join(',')), // Add data rows
    ].join('\n');
    return csvContent;
};

// Handle CSV download
const downloadCSV = () => {
    const csvContent = convertToCSV(filteredUsers);
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'users.csv';
    link.click();
};

return (
    <div>
        <h1>User List</h1>

        <div className="search-download-container">
            <input
                type="text"
                placeholder="Search users (name, email)"
                value={searchQuery}
                onChange={handleSearchChange}
                className="search-input"
            />
            <button onClick={downloadCSV} className="download-button">
                Download as CSV
            </button>
        </div>

        <table className="user-table">
            <thead>
                <tr>
                    <th>Display Name</th>
                    <th>First Name</th>
                    <th>Username</th>
                    <th>Last Login</th>
                    <th>Email</th>
                    <th>Organisation Units</th>
                </tr>
            </thead>
            <tbody>
                {currentUsers.map(user => (
                    <tr key={user.id} onClick={() => handleSelectUser(user)}>
                        <td>{user.displayName || 'N/A'}</td>
                        <td>{user.firstName || 'N/A'}</td>
                        <td>{user.username || 'N/A'}</td>
                        <td>{user.lastLogin || 'N/A'}</td>
                        <td>{user.email || 'N/A'}</td>
                        <td>
                            {user.organisationUnits?.map(unit => unit.name).join(', ') || 'N/A'}
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>

        {selectedUser && (
            <div className="user-details">
                <h2>User Details</h2>
                {isEditing ? (
                    <div className="edit-form">
                        <label>
                            Display Name:
                            <input
                                type="text"
                                name="displayName"
                                value={editedUser.displayName}
                                onChange={handleInputChange}
                            />
                        </label>
                        <label>
                            First Name:
                            <input
                                type="text"
                                name="firstName"
                                value={editedUser.firstName}
                                onChange={handleInputChange}
                            />
                        </label>
                        <label>
                            Email:
                            <input
                                type="email"
                                name="email"
                                value={editedUser.email}
                                onChange={handleInputChange}
                            />
                        </label>
                        <label>
                            Username:
                            <input
                                type="text"
                                name="username"
                                value={editedUser.username}
                                onChange={handleInputChange}
                            />
                        </label>
                        <button onClick={handleEditToggle}>Save Changes</button>
                    </div>
                ) : (
                    <div>
                        <p><strong>Display Name:</strong> {selectedUser.displayName}</p>
                        <p><strong>First Name:</strong> {selectedUser.firstName}</p>
                        <p><strong>Email:</strong> {selectedUser.email}</p>
                        <p><strong>Username:</strong> {selectedUser.username}</p>
                        <p><strong>Last Login:</strong> {selectedUser.lastLogin}</p>
                        <p><strong>Organisation Units:</strong> {selectedUser.organisationUnits?.map(unit => unit.name).join(', ')}</p>
                        <button onClick={handleEditToggle}>Edit User</button>
                    </div>
                )}
            </div>
        )}

        <div className="pagination">
            <button onClick={() => handlePageChange(1)} disabled={currentPage === 1}>First</button>
            <button onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1}>Previous</button>
            <span>Page {currentPage} of {totalPages}</span>
            <button onClick={() => handlePageChange(currentPage + 1)} disabled={currentPage === totalPages}>Next</button>
            <button onClick={() => handlePageChange(totalPages)} disabled={currentPage === totalPages}>Last</button>
        </div>
    </div>
);

};

export default UserTable;

the output is

Thank you for your assistance!

Best regards,

Suggested Modification to Query

Dear @Waheed , Greetings

I hope this message finds you well.

Please make the following adjustment to the code, and it should work as expected:

javascript

Copy code

resource: 'users',
params: {
    pageSize: 2500,  // Fetch 2500 users per request (adjust according to your system's limit)
    fields: [
        'id', 
        'firstName', 
        'email', 
        'username', 
        'displayName', 
        'lastLogin', 
        'organisationUnits[id,name]' // Request specific fields for organisation units
    ],
},

This modification ensures that the necessary fields for the organization units are correctly included in the response.

Let me know if you need any further assistance.

Best regards,

4 Likes

Thanks a lot @SouthSudan


Best Regards

3 Likes