Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion models/tracker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ function defineApplication (
issuesId: string
componentsId: string
milestonesId: string
timeReportsId: string
templatesId: string
labelsId: string
}
Expand Down Expand Up @@ -420,6 +421,12 @@ function defineApplication (
icon: tracker.icon.Milestone,
component: tracker.component.Milestones
},
{
id: opt.timeReportsId,
label: tracker.string.TeamTimeReport,
icon: tracker.icon.TimeReport,
component: tracker.component.ProjectTimeReports
},
{
id: opt.templatesId,
label: tracker.string.IssueTemplates,
Expand Down Expand Up @@ -501,6 +508,7 @@ export function createModel (builder: Builder): void {
const issuesId = 'issues'
const componentsId = 'components'
const milestonesId = 'milestones'
const timeReportsId = 'timeReports'
const templatesId = 'templates'
const myIssuesId = 'my-issues'
const allIssuesId = 'all-issues'
Expand Down Expand Up @@ -624,7 +632,16 @@ export function createModel (builder: Builder): void {
tracker.ids.IssueTemplateUpdatedActivityViewlet
)

defineApplication(builder, { myIssuesId, allIssuesId, issuesId, componentsId, milestonesId, templatesId, labelsId })
defineApplication(builder, {
myIssuesId,
allIssuesId,
issuesId,
componentsId,
milestonesId,
timeReportsId,
templatesId,
labelsId
})

defineActions(builder, issuesId, componentsId, myIssuesId)

Expand Down
50 changes: 49 additions & 1 deletion models/tracker/src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,55 @@ import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_TASK, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import tags from '@hcengineering/tags'
import task from '@hcengineering/task'
import tracker, {
import {
type Issue,
type IssueStatus,
type Project,
TimeReportDayType,
trackerId
} from '@hcengineering/tracker'
import workbench, { type Application } from '@hcengineering/workbench'

import { classicIssueTaskStatuses } from '.'
import tracker from './plugin'

const TIME_REPORTS_SPECIAL_ID = 'timeReports'
const PROJECTS_SPACE_ID = 'projects'

/** One-time patch for workspaces created before the tab existed in the model. */
async function addTimeReportsTabToApp (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)

const app = await tx.findOne<Application>(workbench.class.Application, { _id: tracker.app.Tracker })
if (app === undefined) return

const projectsSpace = app?.navigatorModel?.spaces?.find((s) => s.id === PROJECTS_SPACE_ID)
if (projectsSpace === undefined) return
if (projectsSpace.specials?.some((s) => s.id === TIME_REPORTS_SPECIAL_ID) === true) return

const timeReportsTab = {
id: TIME_REPORTS_SPECIAL_ID,
label: tracker.string.TeamTimeReport,
icon: tracker.icon.TimeReport,
component: tracker.component.ProjectTimeReports
}
const specials = [...(projectsSpace.specials ?? [])]
const templatesIdx = specials.findIndex((s) => s.id === 'templates')
if (templatesIdx >= 0) {
specials.splice(templatesIdx, 0, timeReportsTab)
} else {
specials.push(timeReportsTab)
}

const navigatorModel = { ...app.navigatorModel }
if (navigatorModel.spaces != null) {
navigatorModel.spaces = navigatorModel.spaces.map((space) =>
space.id === PROJECTS_SPACE_ID ? { ...space, specials } : space
)
}

await tx.update(app, { navigatorModel })
}

async function createDefaultProject (tx: TxOperations): Promise<void> {
const current = await tx.findOne(tracker.class.Project, {
Expand Down Expand Up @@ -409,6 +449,14 @@ export const trackerOperation: MigrateOperation = {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
}
},
{
state: 'add-time-reports-tab',
func: addTimeReportsTabToApp
},
{
state: 'add-time-reports-tab-v2',
func: addTimeReportsTabToApp
}
])
}
Expand Down
26 changes: 26 additions & 0 deletions plugins/tracker-assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,32 @@
"ReportedTime": "Spent time",
"RemainingTime": "Remaining time",
"TimeSpendReports": "Time spent reports",
"TimeReports": "Time reports",
"TeamTimeReport": "Team time report",
"TimeReportsFilterTask": "Filter by task",
"TimeReportsFilterTaskSearch": "Search task by title",
"TimeReportsAddTask": "Select task",
"TimeReportsClearTasks": "Clear tasks",
"TimeReportsFilterReporter": "Who logged time",
"TimeReportsFilterAssignees": "Filter tasks by assignee",
"TimeReportsFilterTracker": "Filter tasks by time tracker",
"TimeReportsDates": "Dates",
"TimeReportsWithRecordedTime": "With recorded time",
"TimeReportsAllTasks": "All tasks",
"TimeReportsColumnAssignees": "Selected assignees in filter",
"TimeReportsTitle": "Time spent ({count} tasks)",
"TimeReportsEmpty": "No tasks match the selected filters",
"TimeReportsOfMembers": "{selected} people of {total}",
"TimeReportsPrevPage": "Previous page",
"TimeReportsNextPage": "Next page",
"TimeReportsExportExcel": "Export to Excel",
"TimeReportsExportColIssueId": "Issue ID",
"TimeReportsExportColIssueTitle": "Issue title",
"TimeReportsExportColParentId": "Parent issue ID",
"TimeReportsExportColParentTitle": "Parent issue title",
"TimeReportsExportColReporter": "Time logged by",
"TimeReportsExportColHours": "Hours logged",
"TimeReportsExportTotal": "Total",
"TimeSpendReport": "Time",
"TimeSpendReportAdd": "Add time report",
"TimeSpendReportDate": "Date",
Expand Down
26 changes: 26 additions & 0 deletions plugins/tracker-assets/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,32 @@
"ReportedTime": "Потраченное времени",
"RemainingTime": "Осталось времени",
"TimeSpendReports": "Отчеты по времени",
"TimeReports": "Отчёты по времени",
"TeamTimeReport": "Отчёт по времени команды",
"TimeReportsFilterTask": "Фильтр по задаче",
"TimeReportsFilterTaskSearch": "Поиск задачи по названию",
"TimeReportsAddTask": "Выбрать задачу",
"TimeReportsClearTasks": "Сбросить задачи",
"TimeReportsFilterReporter": "Кто списывал время",
"TimeReportsFilterAssignees": "Фильтр задач по исполнителю",
"TimeReportsFilterTracker": "Фильтр задач по таймтрекеру",
"TimeReportsDates": "Даты",
"TimeReportsWithRecordedTime": "С учтённым временем",
"TimeReportsAllTasks": "Все задачи",
"TimeReportsColumnAssignees": "Выбранные исполнители в фильтре",
"TimeReportsTitle": "Затраты времени ({count} задач)",
"TimeReportsEmpty": "Нет задач по выбранным фильтрам",
"TimeReportsOfMembers": "{selected} человек из {total}",
"TimeReportsPrevPage": "Предыдущая страница",
"TimeReportsNextPage": "Следующая страница",
"TimeReportsExportExcel": "Выгрузить в Excel",
"TimeReportsExportColIssueId": "Номер задачи",
"TimeReportsExportColIssueTitle": "Название задачи",
"TimeReportsExportColParentId": "Номер родительской задачи",
"TimeReportsExportColParentTitle": "Название родительской задачи",
"TimeReportsExportColReporter": "Исполнитель (кто списывал время)",
"TimeReportsExportColHours": "Списано часов",
"TimeReportsExportTotal": "Итого",
"TimeSpendReport": "Время",
"TimeSpendReportAdd": "Добавить затраченное время",
"TimeSpendReportDate": "Дата",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import core, { type DocumentQuery, type FindOptions, type Ref, SortingOrder } from '@hcengineering/core'
import { ObjectPopup } from '@hcengineering/presentation'
import { Issue, Project } from '@hcengineering/tracker'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import IssueStatusIcon from '../IssueStatusIcon.svelte'

export let projectId: Ref<Project>
export let ignoreIssues: Ref<Issue>[] = []

const dispatch = createEventDispatcher()

const options: FindOptions<Issue> = {
lookup: {
status: [tracker.class.IssueStatus, { category: core.class.StatusCategory }]
},
sort: { modifiedOn: SortingOrder.Descending }
}

$: docQuery = { space: projectId } satisfies DocumentQuery<Issue>
</script>

<ObjectPopup
_class={tracker.class.Issue}
{options}
{docQuery}
ignoreObjects={ignoreIssues}
category={tracker.completion.IssueCategory}
multiSelect={false}
closeAfterSelect={true}
placeholder={tracker.string.TimeReportsAddTask}
searchMode="field"
searchField="title"
width="large"
shadows={true}
on:close={(e) => dispatch('close', e.detail)}
>
<svelte:fragment slot="item" let:item={issue}>
<div class="flex-center clear-mins w-full h-9">
{#if issue?.$lookup?.status}
<div class="icon mr-4 h-8">
<IssueStatusIcon value={issue.$lookup.status} taskType={issue.kind} space={issue.space} size="small" />
</div>
{/if}
<span class="overflow-label flex-no-shrink mr-3">{issue.identifier}</span>
<span class="overflow-label w-full content-color">{issue.title}</span>
</div>
</svelte:fragment>
</ObjectPopup>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Ref } from '@hcengineering/core'
import { Project } from '@hcengineering/tracker'
import TimeReportsView from './TimeReportsView.svelte'

export let currentSpace: Ref<Project>
</script>

<div class="project-time-reports">
<TimeReportsView projectId={currentSpace} />
</div>

<style lang="scss">
.project-time-reports {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
height: 100%;
overflow: hidden;
}
</style>
Loading