Skip to content
1 change: 1 addition & 0 deletions modules/issue_tracker/css/issue_card.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
display: flex;
gap: 8px;
margin-bottom: 16px;
flex-wrap: wrap;
}

.issue-content {
Expand Down
22 changes: 15 additions & 7 deletions modules/issue_tracker/css/issue_tracker_batchmode.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,28 @@
}

.filter-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
display: grid;
grid-template-columns: auto auto auto;
padding: 10px;
overflow-y: scroll;
max-height: 300px;
}

.filter-list label {
display: flex;
align-items: center;
margin-bottom: 0;
font-weight: normal;
padding-right: 10px;
}

.filter-list label input {
margin: 0;
margin-right: 4px;
margin-bottom: 4px;
}

.filter-list label span {
margin-left: 3px;
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
}

.issues-list {
Expand Down
7 changes: 5 additions & 2 deletions modules/issue_tracker/jsx/IssueCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import swal from 'sweetalert2';
import Modal from 'jsx/Modal';
import {withTranslation, Trans} from 'react-i18next';
import '../css/issue_card.css';
import Markdown from 'jsx/Markdown';

const IssueCard = React.memo(function IssueCard(props) {
const {t} = props;
Expand Down Expand Up @@ -457,7 +458,8 @@ const IssueCard = React.memo(function IssueCard(props) {
) : (
<div className="description-container">
<p className="description-text">
{description}</p>
<Markdown content={description} />
</p>
</div>
)}
</div>
Expand All @@ -469,7 +471,8 @@ const IssueCard = React.memo(function IssueCard(props) {
issue.topComments.map((comment, index) => (
<div key={index} className="comment">
<p className="comment-text">
{comment.issueComment}</p>
<Markdown content={comment.issueComment} />
</p>
<span className="comment-meta">
<Trans
ns="issue_tracker"
Expand Down
38 changes: 37 additions & 1 deletion modules/issue_tracker/jsx/IssueTrackerBatchMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function IssueTrackerBatchMode({options = {}, t}) {
const [selectedPriorities, setSelectedPriorities] = useState([]);
const [selectedStatuses, setSelectedStatuses] = useState([]);
const [selectedSites, setSelectedSites] = useState([]);
const [selectedAssignees, setSelectedAssignees] = useState([]);
const [filteredIssues, setFilteredIssues] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
Expand Down Expand Up @@ -49,6 +50,7 @@ function IssueTrackerBatchMode({options = {}, t}) {
selectedPriorities,
selectedStatuses,
selectedSites,
selectedAssignees,
issues,
]);

Expand Down Expand Up @@ -102,7 +104,9 @@ function IssueTrackerBatchMode({options = {}, t}) {
(selectedStatuses.length === 0 ||
selectedStatuses.includes(issue.status)) &&
(selectedSites.length === 0 ||
selectedSites.includes(String(issue.centerID)))
selectedSites.includes(String(issue.centerID))) &&
(selectedAssignees.length === 0 ||
selectedAssignees.includes(String(issue.assignee)))
));
}

Expand All @@ -129,6 +133,7 @@ function IssueTrackerBatchMode({options = {}, t}) {
setSelectedPriorities([]);
setSelectedStatuses([]);
setSelectedSites([]);
setSelectedAssignees([]);
}

/**
Expand Down Expand Up @@ -206,6 +211,15 @@ function IssueTrackerBatchMode({options = {}, t}) {
</span>
),
},
{
id: 'assignee', // Added assignee tab
label: (
<span>
Assignee{' '}
<span className="badge bg-primary">{selectedAssignees.length}</span>
</span>
),
},
];

const panelTitle = (
Expand Down Expand Up @@ -316,6 +330,27 @@ function IssueTrackerBatchMode({options = {}, t}) {
))}
</div>
</TabPane>
<TabPane TabId="assignee">
<div className="filter-list">
{Object.entries(assignees).map(([value, label]) => (
<label key={value} className="d-block">
<input
type="checkbox"
checked={selectedAssignees.includes(value)}
onChange={() =>
toggleFilter(
selectedAssignees,
setSelectedAssignees,
value
)
}
className="checkbox me-2"
/>
<span>{label}</span>
</label>
))}
</div>
</TabPane>
</Tabs>
</Panel>
<br/>
Expand Down Expand Up @@ -415,6 +450,7 @@ IssueTrackerBatchMode.propTypes = {
statuses: PropTypes.object,
categories: PropTypes.object,
sites: PropTypes.object,
assignees: PropTypes.object,
}).isRequired,
t: PropTypes.func.isRequired,
};
Expand Down
1 change: 1 addition & 0 deletions modules/issue_tracker/jsx/issueTrackerIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class IssueTrackerIndex extends Component {
statuses: this.state.data.fieldOptions.statuses,
categories: this.state.data.fieldOptions.categories,
sites: this.state.data.fieldOptions.sites,
assignees: this.state.data.fieldOptions.assignees,
}}
/>
</TabPane>
Expand Down
33 changes: 24 additions & 9 deletions modules/issue_tracker/php/edit.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1258,20 +1258,35 @@ class Edit extends \NDB_Page implements ETagCalculator
/**
* Get a formatted string from user information.
*
* @param string $userid a username/userid
* @param string|null $userid a username/userid, or null
*
* @return string a formatted string "fullname (userid)"
*/
function formatUserInformation(string $userid)
function formatUserInformation(?string $userid)
{
$user = \User::factory($userid);
$un = $user->getUsername();
if (empty($un)) {
// e.g. in case of anonymous user
return $user->getFullname();
} else {
return $user->getFullname() . " (" . $un . ")";
if ($userid === null) {
// same as the real name for AnonymousUser object
return "AnonymousUser";
}

// get real name
$db = $this->loris->getdatabaseConnection();
$realName = $db->pselectOne(
"SELECT Real_name
FROM users
WHERE userid = :uid
",
["uid" => $userid]
);

// not found => Anonymous
if ($realName === null) {
// same as the real name for AnonymousUser object
return "AnonymousUser";
}

// else full name + username
return "{$realName} ({$userid})";
}

/**
Expand Down
Loading