mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 08:57:14 +03:00
TSK-413: Implement scrum recording (#2550)
Signed-off-by: Anton Brechka <anton.brechka@xored.com>
This commit is contained in:
parent
7f05cbe497
commit
b0b1089369
@ -16,8 +16,7 @@
|
|||||||
import activity from '@hcengineering/activity'
|
import activity from '@hcengineering/activity'
|
||||||
import { calendarId, Calendar, Event, Reminder } from '@hcengineering/calendar'
|
import { calendarId, Calendar, Event, Reminder } from '@hcengineering/calendar'
|
||||||
import { Employee } from '@hcengineering/contact'
|
import { Employee } from '@hcengineering/contact'
|
||||||
import type { Domain, Markup, Ref, Timestamp } from '@hcengineering/core'
|
import { DateRangeMode, Domain, Markup, Ref, Timestamp, IndexKind } from '@hcengineering/core'
|
||||||
import { IndexKind } from '@hcengineering/core'
|
|
||||||
import {
|
import {
|
||||||
ArrOf,
|
ArrOf,
|
||||||
Builder,
|
Builder,
|
||||||
@ -66,10 +65,10 @@ export class TEvent extends TAttachedDoc implements Event {
|
|||||||
@Index(IndexKind.FullText)
|
@Index(IndexKind.FullText)
|
||||||
location?: string
|
location?: string
|
||||||
|
|
||||||
@Prop(TypeDate(true), calendar.string.Date)
|
@Prop(TypeDate(DateRangeMode.DATETIME), calendar.string.Date)
|
||||||
date!: Timestamp
|
date!: Timestamp
|
||||||
|
|
||||||
@Prop(TypeDate(true), calendar.string.DueTo)
|
@Prop(TypeDate(DateRangeMode.DATETIME), calendar.string.DueTo)
|
||||||
dueDate!: Timestamp
|
dueDate!: Timestamp
|
||||||
|
|
||||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||||
@ -85,7 +84,7 @@ export class TEvent extends TAttachedDoc implements Event {
|
|||||||
@Mixin(calendar.mixin.Reminder, calendar.class.Event)
|
@Mixin(calendar.mixin.Reminder, calendar.class.Event)
|
||||||
@UX(calendar.string.Reminder, calendar.icon.Calendar)
|
@UX(calendar.string.Reminder, calendar.icon.Calendar)
|
||||||
export class TReminder extends TEvent implements Reminder {
|
export class TReminder extends TEvent implements Reminder {
|
||||||
@Prop(TypeDate(true), calendar.string.Shift)
|
@Prop(TypeDate(DateRangeMode.DATETIME), calendar.string.Shift)
|
||||||
@Hidden()
|
@Hidden()
|
||||||
shift!: Timestamp
|
shift!: Timestamp
|
||||||
|
|
||||||
|
@ -30,8 +30,7 @@ import {
|
|||||||
Persons,
|
Persons,
|
||||||
Status
|
Status
|
||||||
} from '@hcengineering/contact'
|
} from '@hcengineering/contact'
|
||||||
import type { Class, Domain, Ref, Timestamp } from '@hcengineering/core'
|
import { Class, DateRangeMode, Domain, Ref, Timestamp, DOMAIN_MODEL, IndexKind } from '@hcengineering/core'
|
||||||
import { DOMAIN_MODEL, IndexKind } from '@hcengineering/core'
|
|
||||||
import {
|
import {
|
||||||
Builder,
|
Builder,
|
||||||
Collection,
|
Collection,
|
||||||
@ -115,7 +114,7 @@ export class TChannel extends TAttachedDoc implements Channel {
|
|||||||
@Model(contact.class.Person, contact.class.Contact)
|
@Model(contact.class.Person, contact.class.Contact)
|
||||||
@UX(contact.string.Person, contact.icon.Person, undefined, 'name')
|
@UX(contact.string.Person, contact.icon.Person, undefined, 'name')
|
||||||
export class TPerson extends TContact implements Person {
|
export class TPerson extends TContact implements Person {
|
||||||
@Prop(TypeDate(false, false), contact.string.Birthday)
|
@Prop(TypeDate(DateRangeMode.DATE, false), contact.string.Birthday)
|
||||||
birthday?: Timestamp
|
birthday?: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Employee } from '@hcengineering/contact'
|
import type { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
|
DateRangeMode,
|
||||||
Domain,
|
Domain,
|
||||||
DOMAIN_MODEL,
|
DOMAIN_MODEL,
|
||||||
FindOptions,
|
FindOptions,
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
TypeNumber,
|
TypeNumber,
|
||||||
TypeRef,
|
TypeRef,
|
||||||
TypeString,
|
TypeString,
|
||||||
|
TypeTimestamp,
|
||||||
UX
|
UX
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import attachment from '@hcengineering/model-attachment'
|
import attachment from '@hcengineering/model-attachment'
|
||||||
@ -65,6 +67,8 @@ import {
|
|||||||
IssueTemplateChild,
|
IssueTemplateChild,
|
||||||
Project,
|
Project,
|
||||||
ProjectStatus,
|
ProjectStatus,
|
||||||
|
Scrum,
|
||||||
|
ScrumRecord,
|
||||||
Sprint,
|
Sprint,
|
||||||
SprintStatus,
|
SprintStatus,
|
||||||
Team,
|
Team,
|
||||||
@ -242,7 +246,7 @@ export class TIssue extends TAttachedDoc implements Issue {
|
|||||||
|
|
||||||
declare space: Ref<Team>
|
declare space: Ref<Team>
|
||||||
|
|
||||||
@Prop(TypeDate(true), tracker.string.DueDate)
|
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
|
||||||
dueDate!: Timestamp | null
|
dueDate!: Timestamp | null
|
||||||
|
|
||||||
@Prop(TypeString(), tracker.string.Rank)
|
@Prop(TypeString(), tracker.string.Rank)
|
||||||
@ -296,7 +300,7 @@ export class TIssueTemplate extends TDoc implements IssueTemplate {
|
|||||||
|
|
||||||
declare space: Ref<Team>
|
declare space: Ref<Team>
|
||||||
|
|
||||||
@Prop(TypeDate(true), tracker.string.DueDate)
|
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.DueDate)
|
||||||
dueDate!: Timestamp | null
|
dueDate!: Timestamp | null
|
||||||
|
|
||||||
@Prop(TypeRef(tracker.class.Sprint), tracker.string.Sprint)
|
@Prop(TypeRef(tracker.class.Sprint), tracker.string.Sprint)
|
||||||
@ -396,10 +400,10 @@ export class TProject extends TDoc implements Project {
|
|||||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||||
attachments?: number
|
attachments?: number
|
||||||
|
|
||||||
@Prop(TypeDate(true), tracker.string.StartDate)
|
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.StartDate)
|
||||||
startDate!: Timestamp | null
|
startDate!: Timestamp | null
|
||||||
|
|
||||||
@Prop(TypeDate(true), tracker.string.TargetDate)
|
@Prop(TypeDate(DateRangeMode.DATETIME), tracker.string.TargetDate)
|
||||||
targetDate!: Timestamp | null
|
targetDate!: Timestamp | null
|
||||||
|
|
||||||
declare space: Ref<Team>
|
declare space: Ref<Team>
|
||||||
@ -433,10 +437,10 @@ export class TSprint extends TDoc implements Sprint {
|
|||||||
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||||
attachments?: number
|
attachments?: number
|
||||||
|
|
||||||
@Prop(TypeDate(false), tracker.string.StartDate)
|
@Prop(TypeDate(), tracker.string.StartDate)
|
||||||
startDate!: Timestamp
|
startDate!: Timestamp
|
||||||
|
|
||||||
@Prop(TypeDate(false), tracker.string.TargetDate)
|
@Prop(TypeDate(), tracker.string.TargetDate)
|
||||||
targetDate!: Timestamp
|
targetDate!: Timestamp
|
||||||
|
|
||||||
declare space: Ref<Team>
|
declare space: Ref<Team>
|
||||||
@ -448,6 +452,62 @@ export class TSprint extends TDoc implements Sprint {
|
|||||||
project!: Ref<Project>
|
project!: Ref<Project>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
@Model(tracker.class.Scrum, core.class.Doc, DOMAIN_TRACKER)
|
||||||
|
@UX(tracker.string.Scrum, tracker.icon.Scrum, tracker.string.Scrum)
|
||||||
|
export class TScrum extends TDoc implements Scrum {
|
||||||
|
@Prop(TypeString(), tracker.string.Title)
|
||||||
|
title!: string
|
||||||
|
|
||||||
|
@Prop(TypeMarkup(), tracker.string.Description)
|
||||||
|
description?: Markup
|
||||||
|
|
||||||
|
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
|
||||||
|
attachments?: number
|
||||||
|
|
||||||
|
@Prop(ArrOf(TypeRef(contact.class.Employee)), tracker.string.Members)
|
||||||
|
members!: Ref<Employee>[]
|
||||||
|
|
||||||
|
@Prop(Collection(tracker.class.Scrum), tracker.string.ScrumRecords)
|
||||||
|
scrumRecords?: number
|
||||||
|
|
||||||
|
@Prop(TypeDate(DateRangeMode.TIME), tracker.string.ScrumBeginTime)
|
||||||
|
beginTime!: Timestamp
|
||||||
|
|
||||||
|
@Prop(TypeDate(DateRangeMode.TIME), tracker.string.ScrumEndTime)
|
||||||
|
endTime!: Timestamp
|
||||||
|
|
||||||
|
declare space: Ref<Team>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
@Model(tracker.class.ScrumRecord, core.class.Doc, DOMAIN_TRACKER)
|
||||||
|
@UX(tracker.string.ScrumRecord, tracker.icon.Scrum, tracker.string.ScrumRecord)
|
||||||
|
export class TScrumRecord extends TAttachedDoc implements ScrumRecord {
|
||||||
|
@Prop(TypeString(), tracker.string.Title)
|
||||||
|
label!: string
|
||||||
|
|
||||||
|
@Prop(TypeTimestamp(), tracker.string.ScrumBeginTime)
|
||||||
|
startTs!: Timestamp
|
||||||
|
|
||||||
|
@Prop(TypeTimestamp(), tracker.string.ScrumEndTime)
|
||||||
|
endTs?: Timestamp
|
||||||
|
|
||||||
|
@Prop(Collection(chunter.class.Comment), tracker.string.Comments)
|
||||||
|
comments!: number
|
||||||
|
|
||||||
|
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments)
|
||||||
|
attachments!: number
|
||||||
|
|
||||||
|
declare attachedTo: Ref<Scrum>
|
||||||
|
declare space: Ref<Team>
|
||||||
|
declare scrumRecorder: Ref<EmployeeAccount>
|
||||||
|
}
|
||||||
|
|
||||||
@UX(core.string.Number)
|
@UX(core.string.Number)
|
||||||
@Model(tracker.class.TypeReportedTime, core.class.Type)
|
@Model(tracker.class.TypeReportedTime, core.class.Type)
|
||||||
export class TTypeReportedTime extends TType {}
|
export class TTypeReportedTime extends TType {}
|
||||||
@ -463,6 +523,8 @@ export function createModel (builder: Builder): void {
|
|||||||
TTypeIssuePriority,
|
TTypeIssuePriority,
|
||||||
TTypeProjectStatus,
|
TTypeProjectStatus,
|
||||||
TSprint,
|
TSprint,
|
||||||
|
TScrum,
|
||||||
|
TScrumRecord,
|
||||||
TTypeSprintStatus,
|
TTypeSprintStatus,
|
||||||
TTimeSpendReport,
|
TTimeSpendReport,
|
||||||
TTypeReportedTime
|
TTypeReportedTime
|
||||||
@ -747,6 +809,7 @@ export function createModel (builder: Builder): void {
|
|||||||
const projectsId = 'projects'
|
const projectsId = 'projects'
|
||||||
const sprintsId = 'sprints'
|
const sprintsId = 'sprints'
|
||||||
const templatesId = 'templates'
|
const templatesId = 'templates'
|
||||||
|
const scrumsId = 'scrums'
|
||||||
|
|
||||||
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: tracker.component.IssuePresenter
|
presenter: tracker.component.IssuePresenter
|
||||||
@ -805,7 +868,7 @@ export function createModel (builder: Builder): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: tracker.component.ProjectTitlePresenter
|
presenter: tracker.component.ProjectPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Team, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(tracker.class.Team, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
@ -819,7 +882,7 @@ export function createModel (builder: Builder): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: tracker.component.SprintTitlePresenter
|
presenter: tracker.component.SprintPresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AttributePresenter, {
|
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
@ -917,6 +980,12 @@ export function createModel (builder: Builder): void {
|
|||||||
icon: tracker.icon.Sprint,
|
icon: tracker.icon.Sprint,
|
||||||
component: tracker.component.Sprints
|
component: tracker.component.Sprints
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: scrumsId,
|
||||||
|
label: tracker.string.Scrums,
|
||||||
|
icon: tracker.icon.Scrum,
|
||||||
|
component: tracker.component.Scrums
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: templatesId,
|
id: templatesId,
|
||||||
label: tracker.string.IssueTemplates,
|
label: tracker.string.IssueTemplates,
|
||||||
|
@ -41,7 +41,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
// Required to pass build without errorsF
|
// Required to pass build without errorsF
|
||||||
Nope: '' as AnyComponent,
|
Nope: '' as AnyComponent,
|
||||||
SprintSelector: '' as AnyComponent,
|
SprintSelector: '' as AnyComponent,
|
||||||
SubIssuesSelector: '' as AnyComponent,
|
|
||||||
IssueStatistics: '' as AnyComponent
|
IssueStatistics: '' as AnyComponent
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
|
@ -196,12 +196,20 @@ export type AttachedData<T extends AttachedDoc> = Omit<T, keyof AttachedDoc>
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
export enum DateRangeMode {
|
||||||
|
DATE = 'date',
|
||||||
|
TIME = 'time',
|
||||||
|
DATETIME = 'datetime'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
export interface TypeDate extends Type<Date> {
|
export interface TypeDate extends Type<Date> {
|
||||||
|
// If not set date mode default
|
||||||
|
mode: DateRangeMode
|
||||||
// If not set to true, will be false
|
// If not set to true, will be false
|
||||||
withTime?: boolean
|
withShift: boolean
|
||||||
// If not set to true, will be false
|
|
||||||
withShift?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +23,7 @@ import core, {
|
|||||||
ClassifierKind,
|
ClassifierKind,
|
||||||
Collection as TypeCollection,
|
Collection as TypeCollection,
|
||||||
Data,
|
Data,
|
||||||
|
DateRangeMode,
|
||||||
Doc,
|
Doc,
|
||||||
Domain,
|
Domain,
|
||||||
Enum,
|
Enum,
|
||||||
@ -405,8 +406,8 @@ export function TypeTimestamp (): Type<Timestamp> {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function TypeDate (withTime?: boolean, withShift?: boolean): TypeDateType {
|
export function TypeDate (mode: DateRangeMode = DateRangeMode.DATE, withShift: boolean = true): TypeDateType {
|
||||||
return { _class: core.class.TypeDate, label: core.string.Date, withTime, withShift }
|
return { _class: core.class.TypeDate, label: core.string.Date, mode, withShift }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
export let isHeader: boolean = true
|
export let isHeader: boolean = true
|
||||||
export let isSub: boolean = true
|
export let isSub: boolean = true
|
||||||
export let isAside: boolean = true
|
export let isAside: boolean = true
|
||||||
|
export let isUtils: boolean = true
|
||||||
export let isCustomAttr: boolean = true
|
export let isCustomAttr: boolean = true
|
||||||
export let floatAside = false
|
export let floatAside = false
|
||||||
export let allowClose = true
|
export let allowClose = true
|
||||||
@ -89,7 +90,7 @@
|
|||||||
<svelte:fragment slot="utils">
|
<svelte:fragment slot="utils">
|
||||||
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
||||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||||
{#if $$slots.utils}
|
{#if isUtils && $$slots.utils}
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
<slot name="utils" />
|
<slot name="utils" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { getFocusManager } from '../focus'
|
import { getFocusManager } from '../focus'
|
||||||
import { showPopup } from '../popups'
|
import { showPopup } from '../popups'
|
||||||
import type { AnySvelteComponent, ButtonKind, ButtonSize, ListItem, TooltipAlignment } from '../types'
|
import type { AnySvelteComponent, ButtonKind, ButtonSize, ListItem, TooltipAlignment } from '../types'
|
||||||
@ -37,6 +38,7 @@
|
|||||||
let container: HTMLElement
|
let container: HTMLElement
|
||||||
let opened: boolean = false
|
let opened: boolean = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
const mgr = getFocusManager()
|
const mgr = getFocusManager()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -53,7 +55,10 @@
|
|||||||
if (!opened) {
|
if (!opened) {
|
||||||
opened = true
|
opened = true
|
||||||
showPopup(DropdownPopup, { title: label, items, icon }, container, (result) => {
|
showPopup(DropdownPopup, { title: label, items, icon }, container, (result) => {
|
||||||
if (result) selected = result
|
if (result) {
|
||||||
|
selected = result
|
||||||
|
dispatch('selected', result)
|
||||||
|
}
|
||||||
opened = false
|
opened = false
|
||||||
mgr?.setFocusPos(focusIndex)
|
mgr?.setFocusPos(focusIndex)
|
||||||
})
|
})
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { showPopup } from '../popups'
|
import { showPopup } from '../popups'
|
||||||
@ -75,7 +76,7 @@
|
|||||||
{#if value?.shift !== undefined}
|
{#if value?.shift !== undefined}
|
||||||
<TimeShiftPresenter value={value.shift} />
|
<TimeShiftPresenter value={value.shift} />
|
||||||
{:else}
|
{:else}
|
||||||
<DateRangePresenter value={value?.date} withTime={true} editable={false} />
|
<DateRangePresenter value={value?.date} mode={DateRangeMode.DATETIME} editable={false} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import ui from '../plugin'
|
import ui from '../plugin'
|
||||||
import { DateOrShift } from '../types'
|
import { DateOrShift } from '../types'
|
||||||
@ -39,7 +40,7 @@
|
|||||||
<div class="flex-center mt-1 mb-1">
|
<div class="flex-center mt-1 mb-1">
|
||||||
<DateRangePresenter
|
<DateRangePresenter
|
||||||
bind:value={date}
|
bind:value={date}
|
||||||
withTime={true}
|
mode={DateRangeMode.DATETIME}
|
||||||
editable={true}
|
editable={true}
|
||||||
labelNull={ui.string.SelectDate}
|
labelNull={ui.string.SelectDate}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import ui from '../../plugin'
|
import ui from '../../plugin'
|
||||||
import Label from '../Label.svelte'
|
import Label from '../Label.svelte'
|
||||||
@ -34,11 +35,13 @@
|
|||||||
dispatch('change', value)
|
dispatch('change', value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: mode = withTime ? DateRangeMode.DATETIME : DateRangeMode.DATE
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiSelect antiWrapper cursor-default">
|
<div class="antiSelect antiWrapper cursor-default">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<span class="label mb-1"><Label label={title} /></span>
|
<span class="label mb-1"><Label label={title} /></span>
|
||||||
<DatePresenter {value} {withTime} {icon} {labelOver} {labelNull} editable on:change={changeValue} />
|
<DatePresenter {value} {mode} {icon} {labelOver} {labelNull} editable on:change={changeValue} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
}
|
}
|
||||||
$: component = $dpstore.component
|
$: component = $dpstore.component
|
||||||
$: shift = $dpstore.shift
|
$: shift = $dpstore.shift
|
||||||
|
$: mode = $dpstore.mode
|
||||||
|
|
||||||
function _update (result: any): void {
|
function _update (result: any): void {
|
||||||
fitPopup()
|
fitPopup()
|
||||||
@ -106,6 +107,7 @@
|
|||||||
{#if component}
|
{#if component}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={component}
|
this={component}
|
||||||
|
bind:mode
|
||||||
bind:shift
|
bind:shift
|
||||||
bind:this={componentInstance}
|
bind:this={componentInstance}
|
||||||
on:update={(ev) => _update(ev.detail)}
|
on:update={(ev) => _update(ev.detail)}
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
import DPCalendarOver from './icons/DPCalendarOver.svelte'
|
import DPCalendarOver from './icons/DPCalendarOver.svelte'
|
||||||
import { getMonthName } from './internal/DateUtils'
|
import { getMonthName } from './internal/DateUtils'
|
||||||
import DatePopup from './DatePopup.svelte'
|
import DatePopup from './DatePopup.svelte'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
|
|
||||||
export let value: number | null | undefined
|
export let value: number | null | undefined
|
||||||
export let withTime: boolean = false
|
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||||
export let mondayStart: boolean = true
|
export let mondayStart: boolean = true
|
||||||
export let editable: boolean = false
|
export let editable: boolean = false
|
||||||
export let icon: 'normal' | 'warning' | 'critical' | 'overdue' = 'normal'
|
export let icon: 'normal' | 'warning' | 'critical' | 'overdue' = 'normal'
|
||||||
@ -58,6 +59,8 @@
|
|||||||
dispatch('change', value)
|
dispatch('change', value)
|
||||||
opened = false
|
opened = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: withTime = mode !== DateRangeMode.DATE
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import ui from '../../plugin'
|
import ui from '../../plugin'
|
||||||
import Label from '../Label.svelte'
|
import Label from '../Label.svelte'
|
||||||
import DateRangePresenter from './DateRangePresenter.svelte'
|
import DateRangePresenter from './DateRangePresenter.svelte'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
|
|
||||||
export let title: IntlString
|
export let title: IntlString
|
||||||
export let value: number | null | undefined = null
|
export let value: number | null | undefined = null
|
||||||
@ -39,6 +40,14 @@
|
|||||||
<div class="antiSelect antiWrapper cursor-default">
|
<div class="antiSelect antiWrapper cursor-default">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<span class="label mb-1"><Label label={title} /></span>
|
<span class="label mb-1"><Label label={title} /></span>
|
||||||
<DateRangePresenter {value} {withTime} {icon} {labelOver} {labelNull} editable on:change={changeValue} />
|
<DateRangePresenter
|
||||||
|
{value}
|
||||||
|
mode={DateRangeMode.DATETIME}
|
||||||
|
{icon}
|
||||||
|
{labelOver}
|
||||||
|
{labelNull}
|
||||||
|
editable
|
||||||
|
on:change={changeValue}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,46 +18,69 @@
|
|||||||
import Month from './Month.svelte'
|
import Month from './Month.svelte'
|
||||||
import Scroller from '../Scroller.svelte'
|
import Scroller from '../Scroller.svelte'
|
||||||
import TimeShiftPresenter from '../TimeShiftPresenter.svelte'
|
import TimeShiftPresenter from '../TimeShiftPresenter.svelte'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
|
|
||||||
export let direction: 'before' | 'after' = 'after'
|
export let direction: 'before' | 'after' = 'after'
|
||||||
export let minutes: number[] = [5, 15, 30]
|
export let minutes: number[] = [5, 15, 30]
|
||||||
export let hours: number[] = [1, 2, 4, 8, 12]
|
export let hours: number[] = [1, 2, 4, 8, 12]
|
||||||
export let days: number[] = [1, 3, 7, 30]
|
export let days: number[] = [1, 3, 7, 30]
|
||||||
export let shift: boolean = false
|
export let shift: boolean = false
|
||||||
|
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||||
|
|
||||||
|
$: withTime = mode !== DateRangeMode.DATE
|
||||||
|
$: withDate = mode !== DateRangeMode.TIME
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const today: Date = new Date(Date.now())
|
const today = new Date(Date.now())
|
||||||
$: currentDate = $dpstore.currentDate ?? today
|
const startDate = new Date(0)
|
||||||
|
|
||||||
|
$: defaultDate =
|
||||||
|
mode === DateRangeMode.TIME
|
||||||
|
? new Date(
|
||||||
|
startDate.getFullYear(),
|
||||||
|
startDate.getMonth(),
|
||||||
|
startDate.getDate(),
|
||||||
|
today.getHours(),
|
||||||
|
today.getMinutes()
|
||||||
|
)
|
||||||
|
: today
|
||||||
|
$: currentDate = $dpstore.currentDate ?? defaultDate
|
||||||
const mondayStart: boolean = true
|
const mondayStart: boolean = true
|
||||||
|
|
||||||
$: base = direction === 'before' ? -1 : 1
|
$: base = direction === 'before' ? -1 : 1
|
||||||
const MINUTE = 60 * 1000
|
const MINUTE = 60 * 1000
|
||||||
const HOUR = 60 * MINUTE
|
const HOUR = 60 * MINUTE
|
||||||
const DAY = 24 * HOUR
|
const DAY = 24 * HOUR
|
||||||
$: values = [
|
|
||||||
...minutes.map((m) => m * MINUTE),
|
const shiftValues: (number | string)[] = []
|
||||||
'divider',
|
|
||||||
...hours.map((m) => m * HOUR),
|
$: {
|
||||||
'divider',
|
if (withTime) {
|
||||||
...days.map((m) => m * DAY)
|
shiftValues.push(...minutes.map((m) => m * MINUTE), 'divider', ...hours.map((m) => m * HOUR))
|
||||||
]
|
}
|
||||||
|
if (withDate) {
|
||||||
|
shiftValues.push('divider', ...days.map((m) => m * DAY))
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="month-popup-container">
|
<div class="month-popup-container">
|
||||||
<Month
|
{#if mode !== DateRangeMode.TIME}
|
||||||
bind:currentDate
|
<Month
|
||||||
{mondayStart}
|
bind:currentDate
|
||||||
on:update={(result) => {
|
{mondayStart}
|
||||||
if (result.detail !== undefined) {
|
on:update={(result) => {
|
||||||
dispatch('close', result.detail)
|
if (result.detail !== undefined) {
|
||||||
}
|
dispatch('close', result.detail)
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{#if shift}
|
{#if shift}
|
||||||
<div class="shift-container">
|
<div class="shift-container">
|
||||||
<Scroller>
|
<Scroller>
|
||||||
{#each values as value}
|
{#each shiftValues as value}
|
||||||
{#if typeof value === 'number'}
|
{#if typeof value === 'number'}
|
||||||
<div
|
<div
|
||||||
class="btn"
|
class="btn"
|
||||||
@ -92,6 +115,7 @@
|
|||||||
top: 1rem;
|
top: 1rem;
|
||||||
right: calc(100% - 0.5rem);
|
right: calc(100% - 0.5rem);
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
|
height: fit-content;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
width: 12rem;
|
width: 12rem;
|
||||||
min-width: 12rem;
|
min-width: 12rem;
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
import DPCalendar from './icons/DPCalendar.svelte'
|
import DPCalendar from './icons/DPCalendar.svelte'
|
||||||
import DPCalendarOver from './icons/DPCalendarOver.svelte'
|
import DPCalendarOver from './icons/DPCalendarOver.svelte'
|
||||||
import DateRangePopup from './DateRangePopup.svelte'
|
import DateRangePopup from './DateRangePopup.svelte'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
|
|
||||||
export let value: number | null | undefined = null
|
export let value: number | null | undefined = null
|
||||||
export let withTime: boolean = false
|
export let mode: DateRangeMode = DateRangeMode.DATE
|
||||||
export let editable: boolean = false
|
export let editable: boolean = false
|
||||||
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
|
||||||
export let labelOver: IntlString | undefined = undefined // label instead of date
|
export let labelOver: IntlString | undefined = undefined // label instead of date
|
||||||
@ -44,9 +45,12 @@
|
|||||||
}
|
}
|
||||||
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
|
const editsType: TEdits[] = ['day', 'month', 'year', 'hour', 'min']
|
||||||
const getIndex = (id: TEdits): number => editsType.indexOf(id)
|
const getIndex = (id: TEdits): number => editsType.indexOf(id)
|
||||||
const today: Date = new Date(Date.now())
|
const today = new Date(Date.now())
|
||||||
|
const startDate = new Date(0)
|
||||||
|
const defaultSelected: TEdits = mode === DateRangeMode.TIME ? 'hour' : 'day'
|
||||||
|
|
||||||
let currentDate: Date
|
let currentDate: Date
|
||||||
let selected: TEdits = 'day'
|
let selected: TEdits = defaultSelected
|
||||||
|
|
||||||
let edit: boolean = false
|
let edit: boolean = false
|
||||||
let opened: boolean = false
|
let opened: boolean = false
|
||||||
@ -58,6 +62,9 @@
|
|||||||
return { id: edit, value: -1 }
|
return { id: edit, value: -1 }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$: withTime = mode !== DateRangeMode.DATE
|
||||||
|
$: withDate = mode !== DateRangeMode.TIME
|
||||||
|
|
||||||
const getValue = (date: Date | null | undefined = today, id: TEdits): number => {
|
const getValue = (date: Date | null | undefined = today, id: TEdits): number => {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'day':
|
case 'day':
|
||||||
@ -126,7 +133,7 @@
|
|||||||
edits.forEach((edit, i) => {
|
edits.forEach((edit, i) => {
|
||||||
tempValues[i] = edit.value > 0 || i > 2 ? edit.value : getValue(currentDate, edit.id)
|
tempValues[i] = edit.value > 0 || i > 2 ? edit.value : getValue(currentDate, edit.id)
|
||||||
})
|
})
|
||||||
currentDate = new Date(tempValues[2], tempValues[1] - 1, tempValues[0], tempValues[3], tempValues[4])
|
setCurrentDate(new Date(tempValues[2], tempValues[1] - 1, tempValues[0], tempValues[3], tempValues[4]))
|
||||||
}
|
}
|
||||||
const isNull = (full: boolean = false): boolean => {
|
const isNull = (full: boolean = false): boolean => {
|
||||||
let result: boolean = false
|
let result: boolean = false
|
||||||
@ -162,7 +169,7 @@
|
|||||||
|
|
||||||
if (!isNull() && edits[2].value > 999) {
|
if (!isNull() && edits[2].value > 999) {
|
||||||
fixEdits()
|
fixEdits()
|
||||||
currentDate = setValue(edits[index].value, currentDate, ed)
|
setCurrentDate(setValue(edits[index].value, currentDate, ed))
|
||||||
$dpstore.currentDate = currentDate
|
$dpstore.currentDate = currentDate
|
||||||
dateToEdits()
|
dateToEdits()
|
||||||
}
|
}
|
||||||
@ -183,17 +190,29 @@
|
|||||||
if (edits[index].value !== -1) {
|
if (edits[index].value !== -1) {
|
||||||
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
|
const val = ev.code === 'ArrowUp' ? edits[index].value + 1 : edits[index].value - 1
|
||||||
if (currentDate) {
|
if (currentDate) {
|
||||||
currentDate = setValue(val, currentDate, ed)
|
setCurrentDate(setValue(val, currentDate, ed))
|
||||||
$dpstore.currentDate = currentDate
|
$dpstore.currentDate = currentDate
|
||||||
dateToEdits()
|
dateToEdits()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ev.code === 'ArrowLeft' && edits[index].el) {
|
if (ev.code === 'ArrowLeft' && edits[index].el) {
|
||||||
selected = index === 0 ? edits[withTime ? 4 : 2].id : edits[index - 1].id
|
if (mode === DateRangeMode.TIME) {
|
||||||
|
selected = index === 3 ? edits[4].id : edits[index - 1].id
|
||||||
|
} else if (mode === DateRangeMode.DATETIME) {
|
||||||
|
selected = index === 0 ? edits[4].id : edits[index - 1].id
|
||||||
|
} else {
|
||||||
|
selected = index === 0 ? edits[2].id : edits[index - 1].id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ev.code === 'ArrowRight' && edits[index].el) {
|
if (ev.code === 'ArrowRight' && edits[index].el) {
|
||||||
selected = index === (withTime ? 4 : 2) ? edits[0].id : edits[index + 1].id
|
if (mode === DateRangeMode.TIME) {
|
||||||
|
selected = index === 4 ? edits[3].id : edits[index + 1].id
|
||||||
|
} else if (mode === DateRangeMode.DATETIME) {
|
||||||
|
selected = index === 4 ? edits[0].id : edits[index + 1].id
|
||||||
|
} else {
|
||||||
|
selected = index === 2 ? edits[0].id : edits[index + 1].id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ev.code === 'Tab') {
|
if (ev.code === 'Tab') {
|
||||||
if ((ed === 'year' && !withTime) || (ed === 'min' && withTime)) closeDP()
|
if ((ed === 'year' && !withTime) || (ed === 'min' && withTime)) closeDP()
|
||||||
@ -220,16 +239,39 @@
|
|||||||
if (tempEl) tempEl.focus()
|
if (tempEl) tempEl.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const setEmptyEdits = () => {
|
||||||
|
edits.forEach((edit, index) => {
|
||||||
|
if (mode !== DateRangeMode.TIME || index > 2) {
|
||||||
|
edit.value = -1
|
||||||
|
} else {
|
||||||
|
edit.value = getValue(startDate, edit.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
edits = edits
|
||||||
|
}
|
||||||
|
|
||||||
|
const setCurrentDate = (date: Date) => {
|
||||||
|
if (mode === DateRangeMode.TIME) {
|
||||||
|
const resultDate = new Date(startDate)
|
||||||
|
resultDate.setHours(date.getHours())
|
||||||
|
resultDate.setMinutes(date.getMinutes())
|
||||||
|
|
||||||
|
currentDate = resultDate
|
||||||
|
} else {
|
||||||
|
currentDate = date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const _change = (result: any): void => {
|
const _change = (result: any): void => {
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
currentDate = result
|
setCurrentDate(result)
|
||||||
saveDate()
|
saveDate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const _close = (result: any): void => {
|
const _close = (result: any): void => {
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
if (result !== null) {
|
if (result !== null) {
|
||||||
currentDate = result
|
setCurrentDate(result)
|
||||||
saveDate()
|
saveDate()
|
||||||
}
|
}
|
||||||
closeDP()
|
closeDP()
|
||||||
@ -244,6 +286,7 @@
|
|||||||
$dpstore.onClose = _close
|
$dpstore.onClose = _close
|
||||||
$dpstore.component = DateRangePopup
|
$dpstore.component = DateRangePopup
|
||||||
$dpstore.shift = !noShift
|
$dpstore.shift = !noShift
|
||||||
|
$dpstore.mode = mode
|
||||||
}
|
}
|
||||||
let popupComp: HTMLElement
|
let popupComp: HTMLElement
|
||||||
$: if (opened && $dpstore.popup) popupComp = $dpstore.popup
|
$: if (opened && $dpstore.popup) popupComp = $dpstore.popup
|
||||||
@ -257,15 +300,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const adaptValue = () => {
|
export const adaptValue = () => {
|
||||||
currentDate = new Date(value ?? Date.now())
|
setCurrentDate(new Date(value ?? Date.now()))
|
||||||
currentDate.setSeconds(0, 0)
|
currentDate.setSeconds(0, 0)
|
||||||
if (value !== null && value !== undefined) {
|
if (value !== null && value !== undefined) {
|
||||||
dateToEdits()
|
dateToEdits()
|
||||||
} else if (value === null) {
|
} else if (value === null) {
|
||||||
edits.forEach((edit) => {
|
setEmptyEdits()
|
||||||
edit.value = -1
|
|
||||||
})
|
|
||||||
currentDate = today
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,46 +323,50 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{#if edit}
|
{#if edit}
|
||||||
<span
|
{#if withDate}
|
||||||
bind:this={edits[0].el}
|
<span
|
||||||
class="digit"
|
bind:this={edits[0].el}
|
||||||
tabindex="0"
|
class="digit"
|
||||||
on:keydown={(ev) => keyDown(ev, edits[0].id)}
|
tabindex="0"
|
||||||
on:focus={() => focused(edits[0].id)}
|
on:keydown={(ev) => keyDown(ev, edits[0].id)}
|
||||||
on:blur={(ev) => unfocus(ev, edits[0].id)}
|
on:focus={() => focused(edits[0].id)}
|
||||||
>
|
on:blur={(ev) => unfocus(ev, edits[0].id)}
|
||||||
{#if edits[0].value > -1}
|
>
|
||||||
{edits[0].value.toString().padStart(2, '0')}
|
{#if edits[0].value > -1}
|
||||||
{:else}DD{/if}
|
{edits[0].value.toString().padStart(2, '0')}
|
||||||
</span>
|
{:else}DD{/if}
|
||||||
<span class="separator">.</span>
|
</span>
|
||||||
<span
|
<span class="separator">.</span>
|
||||||
bind:this={edits[1].el}
|
<span
|
||||||
class="digit"
|
bind:this={edits[1].el}
|
||||||
tabindex="0"
|
class="digit"
|
||||||
on:keydown={(ev) => keyDown(ev, edits[1].id)}
|
tabindex="0"
|
||||||
on:focus={() => focused(edits[1].id)}
|
on:keydown={(ev) => keyDown(ev, edits[1].id)}
|
||||||
on:blur={(ev) => unfocus(ev, edits[1].id)}
|
on:focus={() => focused(edits[1].id)}
|
||||||
>
|
on:blur={(ev) => unfocus(ev, edits[1].id)}
|
||||||
{#if edits[1].value > -1}
|
>
|
||||||
{edits[1].value.toString().padStart(2, '0')}
|
{#if edits[1].value > -1}
|
||||||
{:else}MM{/if}
|
{edits[1].value.toString().padStart(2, '0')}
|
||||||
</span>
|
{:else}MM{/if}
|
||||||
<span class="separator">.</span>
|
</span>
|
||||||
<span
|
<span class="separator">.</span>
|
||||||
bind:this={edits[2].el}
|
<span
|
||||||
class="digit"
|
bind:this={edits[2].el}
|
||||||
tabindex="0"
|
class="digit"
|
||||||
on:keydown={(ev) => keyDown(ev, edits[2].id)}
|
tabindex="0"
|
||||||
on:focus={() => focused(edits[2].id)}
|
on:keydown={(ev) => keyDown(ev, edits[2].id)}
|
||||||
on:blur={(ev) => unfocus(ev, edits[2].id)}
|
on:focus={() => focused(edits[2].id)}
|
||||||
>
|
on:blur={(ev) => unfocus(ev, edits[2].id)}
|
||||||
{#if edits[2].value > -1}
|
>
|
||||||
{edits[2].value.toString().padStart(4, '0')}
|
{#if edits[2].value > -1}
|
||||||
{:else}YYYY{/if}
|
{edits[2].value.toString().padStart(4, '0')}
|
||||||
</span>
|
{:else}YYYY{/if}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
{#if withTime}
|
{#if withTime}
|
||||||
<div class="time-divider" />
|
{#if mode === DateRangeMode.DATETIME}
|
||||||
|
<div class="time-divider" />
|
||||||
|
{/if}
|
||||||
<span
|
<span
|
||||||
bind:this={edits[3].el}
|
bind:this={edits[3].el}
|
||||||
class="digit"
|
class="digit"
|
||||||
@ -356,13 +400,12 @@
|
|||||||
class="close-btn"
|
class="close-btn"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selected = 'day'
|
selected = defaultSelected
|
||||||
startTyping = true
|
startTyping = true
|
||||||
value = null
|
value = null
|
||||||
edits.forEach((edit) => {
|
setEmptyEdits()
|
||||||
edit.value = -1
|
const newFocusElement = edits[mode === DateRangeMode.TIME ? 2 : 0].el
|
||||||
})
|
if (newFocusElement) newFocusElement.focus()
|
||||||
if (edits[0].el) edits[0].el.focus()
|
|
||||||
}}
|
}}
|
||||||
on:blur={(ev) => unfocus(ev, closeBtn)}
|
on:blur={(ev) => unfocus(ev, closeBtn)}
|
||||||
>
|
>
|
||||||
@ -376,14 +419,18 @@
|
|||||||
{#if value !== undefined && value !== null && value.toString() !== ''}
|
{#if value !== undefined && value !== null && value.toString() !== ''}
|
||||||
{#if labelOver !== undefined}
|
{#if labelOver !== undefined}
|
||||||
<Label label={labelOver} />
|
<Label label={labelOver} />
|
||||||
{:else if value}
|
{:else}
|
||||||
{new Date(value).getDate()}
|
{#if withDate}
|
||||||
{getMonthName(new Date(value), 'short')}
|
{new Date(value).getDate()}
|
||||||
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
{getMonthName(new Date(value), 'short')}
|
||||||
{new Date(value).getFullYear()}
|
{#if new Date(value).getFullYear() !== today.getFullYear()}
|
||||||
|
{new Date(value).getFullYear()}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if withTime}
|
{#if withTime}
|
||||||
<div class="time-divider" />
|
{#if withDate}
|
||||||
|
<div class="time-divider" />
|
||||||
|
{/if}
|
||||||
{new Date(value).getHours().toString().padStart(2, '0')}
|
{new Date(value).getHours().toString().padStart(2, '0')}
|
||||||
<span class="separator">:</span>
|
<span class="separator">:</span>
|
||||||
{new Date(value).getMinutes().toString().padStart(2, '0')}
|
{new Date(value).getMinutes().toString().padStart(2, '0')}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import DatePresenter from './DatePresenter.svelte'
|
import DatePresenter from './DatePresenter.svelte'
|
||||||
|
|
||||||
export let value: number | null | undefined
|
export let value: number | null | undefined
|
||||||
@ -20,4 +21,4 @@
|
|||||||
export let editable: boolean = false
|
export let editable: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatePresenter bind:value withTime {mondayStart} {editable} />
|
<DatePresenter bind:value mode={DateRangeMode.DATETIME} {mondayStart} {editable} />
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import type { IntlString } from '@hcengineering/platform'
|
import type { IntlString } from '@hcengineering/platform'
|
||||||
import ui from '../../plugin'
|
import ui from '../../plugin'
|
||||||
import DateRangePresenter from './DateRangePresenter.svelte'
|
import DateRangePresenter from './DateRangePresenter.svelte'
|
||||||
@ -25,4 +26,4 @@
|
|||||||
export let noShift: boolean = false
|
export let noShift: boolean = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DateRangePresenter bind:value withTime {editable} {icon} {labelOver} {labelNull} {noShift} />
|
<DateRangePresenter bind:value mode={DateRangeMode.DATETIME} {editable} {icon} {labelOver} {labelNull} {noShift} />
|
||||||
|
17
packages/ui/src/components/icons/Start.svelte
Normal file
17
packages/ui/src/components/icons/Start.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let size: 'small' | 'medium' | 'large'
|
||||||
|
const fill: string = 'currentColor'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="-5 -3 24 24">
|
||||||
|
<g>
|
||||||
|
<g id="c98_play">
|
||||||
|
<path
|
||||||
|
d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
|
||||||
|
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
|
||||||
|
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g id="Capa_1_78_" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
8
packages/ui/src/components/icons/Stop.svelte
Normal file
8
packages/ui/src/components/icons/Stop.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let size: 'small' | 'medium' | 'large'
|
||||||
|
const fill: string = 'currentColor'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 16 16">
|
||||||
|
<rect width="8" height="8" />
|
||||||
|
</svg>
|
@ -103,6 +103,8 @@ export { default as Chevron } from './components/Chevron.svelte'
|
|||||||
export { default as Timeline } from './components/Timeline.svelte'
|
export { default as Timeline } from './components/Timeline.svelte'
|
||||||
|
|
||||||
export { default as IconAdd } from './components/icons/Add.svelte'
|
export { default as IconAdd } from './components/icons/Add.svelte'
|
||||||
|
export { default as IconStart } from './components/icons/Start.svelte'
|
||||||
|
export { default as IconStop } from './components/icons/Stop.svelte'
|
||||||
export { default as IconBack } from './components/icons/Back.svelte'
|
export { default as IconBack } from './components/icons/Back.svelte'
|
||||||
export { default as IconForward } from './components/icons/Forward.svelte'
|
export { default as IconForward } from './components/icons/Forward.svelte'
|
||||||
export { default as IconClose } from './components/icons/Close.svelte'
|
export { default as IconClose } from './components/icons/Close.svelte'
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { getResource } from '@hcengineering/platform'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import type {
|
import type {
|
||||||
@ -83,6 +84,7 @@ interface IDatePopup {
|
|||||||
anchor: HTMLElement | undefined
|
anchor: HTMLElement | undefined
|
||||||
popup: HTMLElement | undefined
|
popup: HTMLElement | undefined
|
||||||
frendlyFocus: HTMLElement[] | undefined
|
frendlyFocus: HTMLElement[] | undefined
|
||||||
|
mode?: DateRangeMode
|
||||||
onClose?: (result: any) => void
|
onClose?: (result: any) => void
|
||||||
onChange?: (result: any) => void
|
onChange?: (result: any) => void
|
||||||
shift?: boolean
|
shift?: boolean
|
||||||
@ -96,7 +98,8 @@ export const dpstore = writable<IDatePopup>({
|
|||||||
frendlyFocus: undefined,
|
frendlyFocus: undefined,
|
||||||
onClose: undefined,
|
onClose: undefined,
|
||||||
onChange: undefined,
|
onChange: undefined,
|
||||||
shift: undefined
|
shift: undefined,
|
||||||
|
mode: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
export function showDatePopup (
|
export function showDatePopup (
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
import { Class, DateRangeMode, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||||
import { Card, getClient, UserBoxList } from '@hcengineering/presentation'
|
import { Card, getClient, UserBoxList } from '@hcengineering/presentation'
|
||||||
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
||||||
import { tick } from 'svelte'
|
import { tick } from 'svelte'
|
||||||
@ -101,7 +101,7 @@
|
|||||||
value={startDate}
|
value={startDate}
|
||||||
labelNull={ui.string.SelectDate}
|
labelNull={ui.string.SelectDate}
|
||||||
on:change={async (event) => await handleNewStartDate(event.detail)}
|
on:change={async (event) => await handleNewStartDate(event.detail)}
|
||||||
withTime
|
mode={DateRangeMode.DATETIME}
|
||||||
editable
|
editable
|
||||||
/>
|
/>
|
||||||
<DateRangePresenter
|
<DateRangePresenter
|
||||||
@ -109,7 +109,7 @@
|
|||||||
value={dueDate}
|
value={dueDate}
|
||||||
labelNull={calendar.string.DueTo}
|
labelNull={calendar.string.DueTo}
|
||||||
on:change={async (event) => await handleNewDueDate(event.detail)}
|
on:change={async (event) => await handleNewDueDate(event.detail)}
|
||||||
withTime
|
mode={DateRangeMode.DATETIME}
|
||||||
editable
|
editable
|
||||||
/>
|
/>
|
||||||
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
|
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import { Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
import { Class, DateRangeMode, Doc, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||||
import { Card, getClient, UserBoxList } from '@hcengineering/presentation'
|
import { Card, getClient, UserBoxList } from '@hcengineering/presentation'
|
||||||
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
import ui, { EditBox, DateRangePresenter } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
@ -73,7 +73,7 @@
|
|||||||
<EditBox bind:value={title} placeholder={calendar.string.Title} kind={'large-style'} focus />
|
<EditBox bind:value={title} placeholder={calendar.string.Title} kind={'large-style'} focus />
|
||||||
<svelte:fragment slot="pool">
|
<svelte:fragment slot="pool">
|
||||||
<!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> -->
|
<!-- <TimeShiftPicker title={calendar.string.Date} bind:value direction="after" /> -->
|
||||||
<DateRangePresenter bind:value withTime={true} editable={true} labelNull={ui.string.SelectDate} />
|
<DateRangePresenter bind:value mode={DateRangeMode.DATETIME} editable={true} labelNull={ui.string.SelectDate} />
|
||||||
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
|
<UserBoxList bind:items={participants} label={calendar.string.Participants} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Event } from '@hcengineering/calendar'
|
import { Event } from '@hcengineering/calendar'
|
||||||
|
import { DateRangeMode } from '@hcengineering/core'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
import { DateRangePresenter } from '@hcengineering/ui'
|
import { DateRangePresenter } from '@hcengineering/ui'
|
||||||
import calendar from '../plugin'
|
import calendar from '../plugin'
|
||||||
@ -21,10 +22,19 @@
|
|||||||
export let value: Event
|
export let value: Event
|
||||||
export let noShift: boolean = false
|
export let noShift: boolean = false
|
||||||
|
|
||||||
|
let dateRangeMode: DateRangeMode
|
||||||
|
|
||||||
$: date = value ? new Date(value.date) : undefined
|
$: date = value ? new Date(value.date) : undefined
|
||||||
$: dueDate = value ? new Date(value.dueDate ?? value.date) : undefined
|
$: dueDate = value ? new Date(value.dueDate ?? value.date) : undefined
|
||||||
|
|
||||||
$: interval = (value.dueDate ?? value.date) - value.date
|
$: interval = (value.dueDate ?? value.date) - value.date
|
||||||
|
$: {
|
||||||
|
if (date && date.getMinutes() !== 0 && date.getHours() !== 0 && interval < DAY) {
|
||||||
|
dateRangeMode = DateRangeMode.DATETIME
|
||||||
|
} else {
|
||||||
|
dateRangeMode = DateRangeMode.DATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SECOND = 1000
|
const SECOND = 1000
|
||||||
const MINUTE = SECOND * 60
|
const MINUTE = SECOND * 60
|
||||||
@ -46,11 +56,7 @@
|
|||||||
|
|
||||||
<div class="antiSelect">
|
<div class="antiSelect">
|
||||||
{#if date}
|
{#if date}
|
||||||
<DateRangePresenter
|
<DateRangePresenter value={date.getTime()} mode={dateRangeMode} {noShift} />
|
||||||
value={date.getTime()}
|
|
||||||
withTime={date.getMinutes() !== 0 && date.getHours() !== 0 && interval < DAY}
|
|
||||||
{noShift}
|
|
||||||
/>
|
|
||||||
{#if interval > 0}
|
{#if interval > 0}
|
||||||
{#await formatDueDate(interval) then t}
|
{#await formatDueDate(interval) then t}
|
||||||
<span class="ml-2 mr-1 whitespace-nowrap">({t})</span>
|
<span class="ml-2 mr-1 whitespace-nowrap">({t})</span>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Timestamp, TypeDate } from '@hcengineering/core'
|
import { DateRangeMode, Timestamp, TypeDate } from '@hcengineering/core'
|
||||||
import { ticker, tooltip } from '@hcengineering/ui'
|
import { ticker, tooltip } from '@hcengineering/ui'
|
||||||
import { DateEditor } from '@hcengineering/view-resources'
|
import { DateEditor } from '@hcengineering/view-resources'
|
||||||
import EmployeeStatusDueDatePopup from './EmployeeStatusDueDatePopup.svelte'
|
import EmployeeStatusDueDatePopup from './EmployeeStatusDueDatePopup.svelte'
|
||||||
@ -12,7 +12,7 @@
|
|||||||
$: formattedDate = statusDueDate && formatDate(statusDueDate)
|
$: formattedDate = statusDueDate && formatDate(statusDueDate)
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const type = { withTime: true } as TypeDate
|
const type = { mode: DateRangeMode.DATETIME, withShift: true } as TypeDate
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -1,48 +1,51 @@
|
|||||||
<!--
|
<!--
|
||||||
// Copyright © 2022 Hardcore Engineering Inc.
|
// Copyright © 2023 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
// 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
|
// 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
|
// 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
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
//
|
//
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Doc, Ref } from '@hcengineering/core'
|
||||||
import { Project } from '@hcengineering/tracker'
|
|
||||||
import { Button, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
import { Button, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
||||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||||
import contact, { Employee } from '@hcengineering/contact'
|
import contact, { Employee } from '@hcengineering/contact'
|
||||||
import { getClient, UsersPopup } from '@hcengineering/presentation'
|
import { getClient, UsersPopup } from '@hcengineering/presentation'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { IntlString, translate } from '@hcengineering/platform'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../../tracker-resources/src/plugin'
|
||||||
|
|
||||||
export let value: Project
|
export let value: Doc
|
||||||
export let kind: ButtonKind = 'no-border'
|
export let kind: ButtonKind = 'no-border'
|
||||||
export let size: ButtonSize = 'small'
|
export let size: ButtonSize = 'small'
|
||||||
export let justify: 'left' | 'center' = 'center'
|
export let justify: 'left' | 'center' = 'center'
|
||||||
export let width: string | undefined = 'min-content'
|
export let width: string | undefined = 'min-content'
|
||||||
|
export let intlTitle: IntlString
|
||||||
|
export let intlSearchPh: IntlString
|
||||||
|
export let retrieveMembers: (doc: Doc) => Ref<Employee>[]
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
let buttonTitle = ''
|
let buttonTitle = ''
|
||||||
|
|
||||||
$: translate(tracker.string.ProjectMembersTitle, {}).then((res) => {
|
$: members = retrieveMembers(value)
|
||||||
|
$: translate(intlTitle, {}).then((res) => {
|
||||||
buttonTitle = res
|
buttonTitle = res
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleProjectMembersChanged = async (result: Ref<Employee>[] | undefined) => {
|
const handleMembersChanged = async (result: Ref<Employee>[] | undefined) => {
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberToPull = value.members.filter((x) => !result.includes(x))[0]
|
const memberToPull = members.filter((x) => !result.includes(x))[0]
|
||||||
const memberToPush = result.filter((x) => !value.members.includes(x))[0]
|
const memberToPush = result.filter((x) => !members.includes(x))[0]
|
||||||
|
|
||||||
if (memberToPull) {
|
if (memberToPull) {
|
||||||
await client.update(value, { $pull: { members: memberToPull } })
|
await client.update(value, { $pull: { members: memberToPull } })
|
||||||
@ -53,22 +56,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectMembersEditorOpened = async (event: MouseEvent) => {
|
const handleMembersEditorOpened = async (event: MouseEvent) => {
|
||||||
showPopup(
|
showPopup(
|
||||||
UsersPopup,
|
UsersPopup,
|
||||||
{
|
{
|
||||||
_class: contact.class.Employee,
|
_class: contact.class.Employee,
|
||||||
selectedUsers: value.members,
|
selectedUsers: members,
|
||||||
allowDeselect: true,
|
allowDeselect: true,
|
||||||
multiSelect: true,
|
multiSelect: true,
|
||||||
docQuery: {
|
docQuery: {
|
||||||
active: true
|
active: true
|
||||||
},
|
},
|
||||||
placeholder: tracker.string.ProjectMembersSearchPlaceholder
|
placeholder: intlSearchPh
|
||||||
},
|
},
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
undefined,
|
undefined,
|
||||||
handleProjectMembersChanged
|
handleMembersChanged
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -80,5 +83,5 @@
|
|||||||
{justify}
|
{justify}
|
||||||
title={buttonTitle}
|
title={buttonTitle}
|
||||||
icon={tracker.icon.ProjectMembers}
|
icon={tracker.icon.ProjectMembers}
|
||||||
on:click={handleProjectMembersEditorOpened}
|
on:click={handleMembersEditorOpened}
|
||||||
/>
|
/>
|
@ -43,6 +43,7 @@ import EmployeeBrowser from './components/EmployeeBrowser.svelte'
|
|||||||
import EmployeeEditor from './components/EmployeeEditor.svelte'
|
import EmployeeEditor from './components/EmployeeEditor.svelte'
|
||||||
import EmployeePresenter from './components/EmployeePresenter.svelte'
|
import EmployeePresenter from './components/EmployeePresenter.svelte'
|
||||||
import MemberPresenter from './components/MemberPresenter.svelte'
|
import MemberPresenter from './components/MemberPresenter.svelte'
|
||||||
|
import MembersPresenter from './components/MembersPresenter.svelte'
|
||||||
import Members from './components/Members.svelte'
|
import Members from './components/Members.svelte'
|
||||||
import OrganizationEditor from './components/OrganizationEditor.svelte'
|
import OrganizationEditor from './components/OrganizationEditor.svelte'
|
||||||
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
|
import OrganizationPresenter from './components/OrganizationPresenter.svelte'
|
||||||
@ -68,7 +69,9 @@ export {
|
|||||||
MemberPresenter,
|
MemberPresenter,
|
||||||
EmployeeEditor,
|
EmployeeEditor,
|
||||||
EmployeeAccountRefPresenter,
|
EmployeeAccountRefPresenter,
|
||||||
EditPerson
|
MembersPresenter,
|
||||||
|
EditPerson,
|
||||||
|
EmployeeRefPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||||
@ -158,6 +161,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
EmployeeRefPresenter,
|
EmployeeRefPresenter,
|
||||||
Members,
|
Members,
|
||||||
MemberPresenter,
|
MemberPresenter,
|
||||||
|
MembersPresenter,
|
||||||
EditMember,
|
EditMember,
|
||||||
EmployeeArrayEditor,
|
EmployeeArrayEditor,
|
||||||
EmployeeEditor,
|
EmployeeEditor,
|
||||||
|
@ -208,7 +208,8 @@ const contactPlugin = plugin(contactId, {
|
|||||||
SocialEditor: '' as AnyComponent,
|
SocialEditor: '' as AnyComponent,
|
||||||
CreateOrganization: '' as AnyComponent,
|
CreateOrganization: '' as AnyComponent,
|
||||||
CreatePerson: '' as AnyComponent,
|
CreatePerson: '' as AnyComponent,
|
||||||
ChannelsPresenter: '' as AnyComponent
|
ChannelsPresenter: '' as AnyComponent,
|
||||||
|
MembersPresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
channelProvider: {
|
channelProvider: {
|
||||||
Email: '' as Ref<ChannelProvider>,
|
Email: '' as Ref<ChannelProvider>,
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
|
|
||||||
<DateRangePresenter
|
<DateRangePresenter
|
||||||
value={_value}
|
value={_value}
|
||||||
withTime={false}
|
|
||||||
editable
|
editable
|
||||||
{kind}
|
{kind}
|
||||||
{noShift}
|
{noShift}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import calendar from '@hcengineering/calendar'
|
import calendar from '@hcengineering/calendar'
|
||||||
import type { Contact, EmployeeAccount, Organization, Person } from '@hcengineering/contact'
|
import type { Contact, EmployeeAccount, Organization, Person } from '@hcengineering/contact'
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Account, Class, Client, Doc, generateId, getCurrentAccount, Ref } from '@hcengineering/core'
|
import { Account, Class, Client, Doc, generateId, getCurrentAccount, Ref, DateRangeMode } from '@hcengineering/core'
|
||||||
import { getResource, OK, Resource, Severity, Status } from '@hcengineering/platform'
|
import { getResource, OK, Resource, Severity, Status } from '@hcengineering/platform'
|
||||||
import { Card, getClient, UserBox, UserBoxList } from '@hcengineering/presentation'
|
import { Card, getClient, UserBox, UserBoxList } from '@hcengineering/presentation'
|
||||||
import type { Candidate, Review } from '@hcengineering/recruit'
|
import type { Candidate, Review } from '@hcengineering/recruit'
|
||||||
@ -173,11 +173,16 @@
|
|||||||
<DateRangePresenter
|
<DateRangePresenter
|
||||||
bind:value={startDate}
|
bind:value={startDate}
|
||||||
labelNull={recruit.string.StartDate}
|
labelNull={recruit.string.StartDate}
|
||||||
withTime
|
mode={DateRangeMode.DATETIME}
|
||||||
editable
|
editable
|
||||||
on:change={updateStart}
|
on:change={updateStart}
|
||||||
/>
|
/>
|
||||||
<DateRangePresenter bind:value={dueDate} labelNull={recruit.string.DueDate} withTime editable />
|
<DateRangePresenter
|
||||||
|
bind:value={dueDate}
|
||||||
|
labelNull={recruit.string.DueDate}
|
||||||
|
mode={DateRangeMode.DATETIME}
|
||||||
|
editable
|
||||||
|
/>
|
||||||
<UserBoxList bind:items={doc.participants} label={calendar.string.Participants} />
|
<UserBoxList bind:items={doc.participants} label={calendar.string.Participants} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"Custom": "Custom",
|
"Custom": "Custom",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
"WithTime": "WithTime",
|
"WithTime": "WithTime",
|
||||||
|
"DateMode": "Date mode",
|
||||||
"CreatingAttribute": "Creating an attribute",
|
"CreatingAttribute": "Creating an attribute",
|
||||||
"EditAttribute": "Edit attribute",
|
"EditAttribute": "Edit attribute",
|
||||||
"CreateEnum": "Create enum",
|
"CreateEnum": "Create enum",
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"Custom": "Пользовательский",
|
"Custom": "Пользовательский",
|
||||||
"Type": "Тип",
|
"Type": "Тип",
|
||||||
"WithTime": "Со временем",
|
"WithTime": "Со временем",
|
||||||
|
"DateMode": "Тип времени",
|
||||||
"CreatingAttribute": "Создание атрибута",
|
"CreatingAttribute": "Создание атрибута",
|
||||||
"EditAttribute": "Редактирование атрибута",
|
"EditAttribute": "Редактирование атрибута",
|
||||||
"CreateEnum": "Создать справочник",
|
"CreateEnum": "Создать справочник",
|
||||||
|
@ -13,40 +13,58 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TypeDate as DateType } from '@hcengineering/core'
|
import { DateRangeMode, TypeDate as DateType } from '@hcengineering/core'
|
||||||
import { TypeDate } from '@hcengineering/model'
|
import { TypeDate } from '@hcengineering/model'
|
||||||
import { Label } from '@hcengineering/ui'
|
import { Label, ListItem } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import setting from '../../plugin'
|
import setting from '../../plugin'
|
||||||
import BooleanEditor from '@hcengineering/view-resources/src/components/BooleanEditor.svelte'
|
import Dropdown from '@hcengineering/ui/src/components/Dropdown.svelte'
|
||||||
import BooleanPresenter from '@hcengineering/view-resources/src/components/BooleanPresenter.svelte'
|
import StringPresenter from '@hcengineering/view-resources/src/components/StringPresenter.svelte'
|
||||||
|
|
||||||
export let type: DateType | undefined
|
export let type: DateType | undefined
|
||||||
export let editable: boolean = true
|
export let editable: boolean = true
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
let withTime: boolean = type?.withTime ?? false
|
const dispatch = createEventDispatcher()
|
||||||
|
const items: ListItem[] = [
|
||||||
|
{
|
||||||
|
_id: DateRangeMode.DATE,
|
||||||
|
label: DateRangeMode.DATE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: DateRangeMode.TIME,
|
||||||
|
label: DateRangeMode.TIME
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: DateRangeMode.DATETIME,
|
||||||
|
label: DateRangeMode.DATETIME
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let selected = items.find((item) => item._id === type?.mode)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
dispatch('change', { type: TypeDate(withTime) })
|
dispatch('change', { type: TypeDate() })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<Label label={setting.string.WithTime} />
|
<Label label={setting.string.DateMode} />
|
||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
{#if editable}
|
{#if editable}
|
||||||
<BooleanEditor
|
<Dropdown
|
||||||
withoutUndefined
|
{selected}
|
||||||
bind:value={withTime}
|
{items}
|
||||||
onChange={(e) => {
|
size="medium"
|
||||||
dispatch('change', { type: TypeDate(e) })
|
placeholder={setting.string.DateMode}
|
||||||
|
on:selected={(res) => {
|
||||||
|
selected = res.detail
|
||||||
|
dispatch('change', { type: TypeDate(res.detail._id) })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<BooleanPresenter value={withTime} />
|
<StringPresenter value={selected?.label ?? ''} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +37,7 @@ export default mergeIds(settingId, setting, {
|
|||||||
Attributes: '' as IntlString,
|
Attributes: '' as IntlString,
|
||||||
Custom: '' as IntlString,
|
Custom: '' as IntlString,
|
||||||
WithTime: '' as IntlString,
|
WithTime: '' as IntlString,
|
||||||
|
DateMode: '' as IntlString,
|
||||||
Type: '' as IntlString,
|
Type: '' as IntlString,
|
||||||
CreatingAttribute: '' as IntlString,
|
CreatingAttribute: '' as IntlString,
|
||||||
EditAttribute: '' as IntlString,
|
EditAttribute: '' as IntlString,
|
||||||
|
@ -3,6 +3,25 @@
|
|||||||
<path d="M17.1,1.2h-4.2C9,1.2,7.2,3,7.2,6.9v0.4H6.9C3,7.2,1.2,9,1.2,12.9v4.2c0,3.9,1.7,5.7,5.6,5.7h4.2 c3.9,0,5.6-1.7,5.6-5.7v-0.3h0.3c3.9,0,5.7-1.7,5.7-5.6V6.9C22.8,3,21,1.2,17.1,1.2z M15.2,17.1c0,3.1-1,4.2-4.1,4.2H6.9 c-3.1,0-4.1-1-4.1-4.2v-4.2c0-3.1,1-4.1,4.1-4.1H8h3.1c3.1,0,4.1,1,4.1,4.1V16V17.1z M21.2,11.1c0,3.1-1,4.1-4.2,4.1h-0.3v-2.4 c0-3.9-1.7-5.6-5.6-5.6H8.8V6.9c0-3.1,1-4.1,4.1-4.1h4.2c3.1,0,4.2,1,4.2,4.1V11.1z" />
|
<path d="M17.1,1.2h-4.2C9,1.2,7.2,3,7.2,6.9v0.4H6.9C3,7.2,1.2,9,1.2,12.9v4.2c0,3.9,1.7,5.7,5.6,5.7h4.2 c3.9,0,5.6-1.7,5.6-5.7v-0.3h0.3c3.9,0,5.7-1.7,5.7-5.6V6.9C22.8,3,21,1.2,17.1,1.2z M15.2,17.1c0,3.1-1,4.2-4.1,4.2H6.9 c-3.1,0-4.1-1-4.1-4.2v-4.2c0-3.1,1-4.1,4.1-4.1H8h3.1c3.1,0,4.1,1,4.1,4.1V16V17.1z M21.2,11.1c0,3.1-1,4.1-4.2,4.1h-0.3v-2.4 c0-3.9-1.7-5.6-5.6-5.6H8.8V6.9c0-3.1,1-4.1,4.1-4.1h4.2c3.1,0,4.2,1,4.2,4.1V11.1z" />
|
||||||
<path d="M11.4,12.5L8,15.9l-1.4-1.4c-0.3-0.3-0.8-0.3-1.1,0s-0.3,0.8,0,1.1l2,2c0.1,0.1,0.3,0.2,0.5,0.2h0 c0.2,0,0.4-0.1,0.5-0.2l3.9-3.9c0.3-0.3,0.3-0.8,0-1.1C12.2,12.2,11.7,12.2,11.4,12.5z" />
|
<path d="M11.4,12.5L8,15.9l-1.4-1.4c-0.3-0.3-0.8-0.3-1.1,0s-0.3,0.8,0,1.1l2,2c0.1,0.1,0.3,0.2,0.5,0.2h0 c0.2,0,0.4-0.1,0.5-0.2l3.9-3.9c0.3-0.3,0.3-0.8,0-1.1C12.2,12.2,11.7,12.2,11.4,12.5z" />
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="scrum" viewBox="0 0 16 16">
|
||||||
|
<g transform="matrix(0.00357, 0, 0, -0.003573, -1.147394, 17.149212)">
|
||||||
|
<path d="M1001 4784 c-253 -68 -410 -331 -346 -579 45 -170 180 -306 347 -349 l52 -13 -110 -5 c-182 -9 -312 -66 -439 -194 -51 -51 -82 -93 -113 -155 -67 -132 -73 -183 -70 -530 3 -283 4 -299 24 -325 11 -15 33 -37 48 -48 27 -20 40 -21 541 -24 l513 -3 -43 -92 c-78 -167 -113 -310 -122 -492 -16 -344 108 -676 349 -930 l80 -85 -645 -2 c-634 -3 -646 -3 -673 -24 -53 -39 -69 -71 -69 -134 0 -63 16 -95 69 -134 l27 -21 1916 -3 1915 -2 -40 -44 c-68 -74 -69 -161 -3 -227 41 -41 88 -55 149 -44 31 5 67 36 234 203 281 281 281 263 0 544 -167 167 -203 198 -234 203 -61 11 -108 -3 -149 -44 -66 -66 -65 -153 3 -227 l41 -44 -423 0 -422 0 80 85 c135 142 225 293 287 478 102 308 81 642 -60 944 l-43 92 513 3 c501 3 514 4 541 24 15 11 37 33 48 48 20 26 21 40 21 359 l0 332 -27 73 c-38 105 -83 176 -158 252 -123 122 -255 179 -434 188 l-110 5 53 14 c167 42 301 179 346 350 65 249 -94 511 -350 578 -251 65 -513 -94 -580 -350 -64 -249 95 -514 347 -579 l52 -13 -110 -6 c-126 -6 -206 -29 -305 -87 -76 -45 -162 -126 -204 -193 -17 -26 -32 -47 -35 -47 -3 0 -16 19 -30 42 -38 63 -134 154 -208 197 -100 59 -179 82 -306 88 l-110 6 52 13 c252 65 411 330 347 579 -67 256 -329 415 -580 350 -256 -67 -415 -329 -350 -578 45 -171 179 -308 346 -350 l53 -14 -110 -6 c-126 -6 -206 -29 -305 -88 -73 -42 -170 -134 -209 -198 -14 -22 -27 -41 -30 -41 -3 0 -18 20 -33 45 -42 66 -132 151 -206 195 -99 58 -179 81 -305 87 l-110 6 53 14 c251 64 410 330 346 576 -67 259 -333 419 -584 351z m182 -318 c103 -43 128 -177 48 -257 -112 -113 -296 -12 -267 146 18 94 128 150 219 111z m1440 0 c103 -43 128 -177 48 -257 -112 -113 -296 -12 -267 146 18 94 128 150 219 111z m1440 0 c103 -43 128 -177 48 -257 -112 -113 -296 -12 -267 146 18 94 128 150 219 111z m-2653 -975 c76 -40 115 -77 151 -143 l34 -63 3 -202 3 -203 -481 0 -481 0 3 203 c3 188 5 205 27 249 40 82 120 150 211 179 14 4 129 7 255 6 221 -2 232 -3 275 -26z m1440 0 c76 -40 115 -77 150 -143 30 -55 35 -75 38 -152 4 -79 3 -88 -12 -83 -129 50 -328 87 -466 87 -134 0 -341 -38 -464 -86 -18 -7 -18 -2 -14 82 7 135 64 231 170 285 71 37 85 38 323 36 221 -2 232 -3 275 -26z m1440 0 c76 -40 115 -77 151 -143 l34 -63 3 -202 3 -203 -481 0 -481 0 3 203 c3 188 5 205 27 249 40 82 121 150 211 179 14 4 129 7 255 6 221 -2 232 -3 275 -26z m-1499 -640 c179 -46 319 -127 449 -260 129 -130 212 -278 257 -457 24 -95 24 -333 0 -428 -46 -182 -128 -328 -261 -462 -134 -133 -280 -215 -462 -261 -95 -24 -333 -24 -428 0 -182 46 -328 128 -462 261 -133 134 -215 280 -261 462 -24 95 -24 333 0 428 45 179 128 327 257 457 147 150 309 236 513 275 100 19 299 12 398 -15z"/>
|
||||||
|
<path d="M2980 2549 c-14 -5 -128 -113 -255 -239 l-230 -229 -237 -3 c-221 -3 -238 -4 -264 -24 -53 -39 -69 -71 -69 -134 0 -63 16 -95 69 -134 26 -20 43 -21 304 -24 171 -2 290 1 312 7 26 8 103 79 307 283 292 293 295 297 279 383 -17 91 -127 149 -216 114z"/>
|
||||||
|
</g>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="start" viewBox="-5 -3 24 24">
|
||||||
|
<g>
|
||||||
|
<g id="c98_play">
|
||||||
|
<path d="M2.067,0.043C2.21-0.028,2.372-0.008,2.493,0.085l13.312,8.503c0.094,0.078,0.154,0.191,0.154,0.313
|
||||||
|
c0,0.12-0.061,0.237-0.154,0.314L2.492,17.717c-0.07,0.057-0.162,0.087-0.25,0.087l-0.176-0.04
|
||||||
|
c-0.136-0.065-0.222-0.207-0.222-0.361V0.402C1.844,0.25,1.93,0.107,2.067,0.043z"/>
|
||||||
|
</g>
|
||||||
|
<g id="Capa_1_78_" />
|
||||||
|
</g>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="stop" viewBox="-4 -4 16 16">
|
||||||
|
<rect width="8" height="8"/>
|
||||||
|
</symbol>
|
||||||
<symbol id="project" viewBox="0 0 16 16">
|
<symbol id="project" viewBox="0 0 16 16">
|
||||||
<path d="M11.7,3.6h-1.1C10.4,2.7,9.7,2,8.8,2H7.2C6.3,2,5.6,2.7,5.4,3.6H4.3C3,3.6,2,4.6,2,5.9V8c0,0.2,0.1,0.3,0.2,0.4 C3.8,9.3,5.9,9.9,8,9.9c2.1,0,4.2-0.5,5.8-1.5C13.9,8.3,14,8.2,14,8V5.9C14,4.6,13,3.6,11.7,3.6z M7.2,3h1.5c0.4,0,0.6,0.2,0.8,0.6 H6.5C6.6,3.2,6.9,3,7.2,3z M13,7.7c-1.4,0.8-3.2,1.2-5,1.2c-1.8,0-3.6-0.4-5-1.2V5.9c0-0.7,0.6-1.3,1.3-1.3h7.4 c0.7,0,1.3,0.6,1.3,1.3V7.7z"/>
|
<path d="M11.7,3.6h-1.1C10.4,2.7,9.7,2,8.8,2H7.2C6.3,2,5.6,2.7,5.4,3.6H4.3C3,3.6,2,4.6,2,5.9V8c0,0.2,0.1,0.3,0.2,0.4 C3.8,9.3,5.9,9.9,8,9.9c2.1,0,4.2-0.5,5.8-1.5C13.9,8.3,14,8.2,14,8V5.9C14,4.6,13,3.6,11.7,3.6z M7.2,3h1.5c0.4,0,0.6,0.2,0.8,0.6 H6.5C6.6,3.2,6.9,3,7.2,3z M13,7.7c-1.4,0.8-3.2,1.2-5,1.2c-1.8,0-3.6-0.4-5-1.2V5.9c0-0.7,0.6-1.3,1.3-1.3h7.4 c0.7,0,1.3,0.6,1.3,1.3V7.7z"/>
|
||||||
<path d="M13.5,9.7c-0.3,0-0.5,0.2-0.5,0.5l-0.1,1.5c-0.1,0.8-0.7,1.3-1.4,1.3H4.5c-0.7,0-1.4-0.6-1.4-1.3L3,10.1 c0-0.3-0.3-0.5-0.5-0.5C2.2,9.7,2,9.9,2,10.2l0.1,1.5C2.2,13,3.3,14,4.5,14h6.9c1.3,0,2.3-1,2.4-2.3l0.1-1.5 C14,9.9,13.8,9.7,13.5,9.7z"/>
|
<path d="M13.5,9.7c-0.3,0-0.5,0.2-0.5,0.5l-0.1,1.5c-0.1,0.8-0.7,1.3-1.4,1.3H4.5c-0.7,0-1.4-0.6-1.4-1.3L3,10.1 c0-0.3-0.3-0.5-0.5-0.5C2.2,9.7,2,9.9,2,10.2l0.1,1.5C2.2,13,3.3,14,4.5,14h6.9c1.3,0,2.3-1,2.4-2.3l0.1-1.5 C14,9.9,13.8,9.7,13.5,9.7z"/>
|
||||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 34 KiB |
@ -221,6 +221,26 @@
|
|||||||
"MoveAndDeleteSprint": "Move Issues to {newSprint} and Delete {deleteSprint}",
|
"MoveAndDeleteSprint": "Move Issues to {newSprint} and Delete {deleteSprint}",
|
||||||
"MoveAndDeleteSprintConfirm": "Do you want to delete sprint and move issues to another sprint?",
|
"MoveAndDeleteSprintConfirm": "Do you want to delete sprint and move issues to another sprint?",
|
||||||
|
|
||||||
|
"Scrums": "Scrums",
|
||||||
|
"Scrum": "Scrum",
|
||||||
|
"ScrumMembersTitle": "Scrum members",
|
||||||
|
"ScrumMembersSearchPlaceholder": "Change scrum members\u2026",
|
||||||
|
"ScrumBeginTime": "Scrum begin time",
|
||||||
|
"ScrumEndTime": "Scrum end time",
|
||||||
|
"NewScrum": "New scrum",
|
||||||
|
"CreateScrum": "Create scrum",
|
||||||
|
"ScrumTitlePlaceholder": "Scrum title",
|
||||||
|
"ScrumDescriptionPlaceholder": "Add scrum description",
|
||||||
|
"ScrumRecords": "Scrum records",
|
||||||
|
"ScrumRecord": "Scrum record",
|
||||||
|
"StartRecord": "Start recording",
|
||||||
|
"StopRecord": "Stop recording",
|
||||||
|
"ChangeScrumRecord": "Start recording another scrum",
|
||||||
|
"ChangeScrumRecordConfirm": "Do you want to stop recording {previousRecord} and start recording {newRecord}?",
|
||||||
|
"ScrumRecorder": "Scrum recorder",
|
||||||
|
"ScrumRecordTimeReports": "Recorded time reports",
|
||||||
|
"ScrumRecordObjects": "Changed objects",
|
||||||
|
|
||||||
"Estimation": "Estimation",
|
"Estimation": "Estimation",
|
||||||
"ReportedTime": "Reported Time",
|
"ReportedTime": "Reported Time",
|
||||||
"TimeSpendReports": "Time spend reports",
|
"TimeSpendReports": "Time spend reports",
|
||||||
|
@ -221,6 +221,26 @@
|
|||||||
"MoveAndDeleteSprint": "Переместить Задачи в {newSprint} и Удалить {deleteSprint}",
|
"MoveAndDeleteSprint": "Переместить Задачи в {newSprint} и Удалить {deleteSprint}",
|
||||||
"MoveAndDeleteSprintConfirm": "Вы действительно хотите удалить спринт и перенести задачи в другой спринт?",
|
"MoveAndDeleteSprintConfirm": "Вы действительно хотите удалить спринт и перенести задачи в другой спринт?",
|
||||||
|
|
||||||
|
"Scrums": "Скрамы",
|
||||||
|
"Scrum": "Скрам",
|
||||||
|
"ScrumMembersTitle": "Участники скрама",
|
||||||
|
"ScrumMembersSearchPlaceholder": "Изменить участников скрама\u2026",
|
||||||
|
"ScrumBeginTime": "Время начала скрама",
|
||||||
|
"ScrumEndTime": "Время конца скрама",
|
||||||
|
"NewScrum": "Новый скрам",
|
||||||
|
"CreateScrum": "Создать скрам",
|
||||||
|
"ScrumTitlePlaceholder": "Название скрама",
|
||||||
|
"ScrumDescriptionPlaceholder": "Описание скрама",
|
||||||
|
"ScrumRecords": "Записи скрамов",
|
||||||
|
"ScrumRecord": "Запись скрама",
|
||||||
|
"StartRecord": "Начать запись",
|
||||||
|
"StopRecord": " Закончить запись",
|
||||||
|
"ChangeScrumRecord": "Начать запись другого скрама",
|
||||||
|
"ChangeScrumRecordConfirm": "Вы действительно хотите прекратить запись {previousScrumRecord} и начать записывать {newScrumRecord}?",
|
||||||
|
"ScrumRecorder": "Ведущий скрама",
|
||||||
|
"ScrumRecordTimeReports": "Временные отчеты",
|
||||||
|
"ScrumRecordObjects": "Измененные объекты",
|
||||||
|
|
||||||
"Estimation": "Оценка",
|
"Estimation": "Оценка",
|
||||||
"ReportedTime": "Использовано",
|
"ReportedTime": "Использовано",
|
||||||
"TimeSpendReports": "Отчеты по времени",
|
"TimeSpendReports": "Отчеты по времени",
|
||||||
|
@ -36,6 +36,9 @@ loadMetadata(tracker.icon, {
|
|||||||
DueDate: `${icons}#inbox`, // TODO: add icon
|
DueDate: `${icons}#inbox`, // TODO: add icon
|
||||||
Parent: `${icons}#myissues`, // TODO: add icon
|
Parent: `${icons}#myissues`, // TODO: add icon
|
||||||
Sprint: `${icons}#sprint`,
|
Sprint: `${icons}#sprint`,
|
||||||
|
Scrum: `${icons}#scrum`,
|
||||||
|
Start: `${icons}#start`,
|
||||||
|
Stop: `${icons}#stop`,
|
||||||
|
|
||||||
CategoryBacklog: `${icons}#status-backlog`,
|
CategoryBacklog: `${icons}#status-backlog`,
|
||||||
CategoryUnstarted: `${icons}#status-todo`,
|
CategoryUnstarted: `${icons}#status-todo`,
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"@hcengineering/notification": "^0.6.5",
|
"@hcengineering/notification": "^0.6.5",
|
||||||
"@hcengineering/notification-resources": "^0.6.0",
|
"@hcengineering/notification-resources": "^0.6.0",
|
||||||
"@hcengineering/contact": "^0.6.9",
|
"@hcengineering/contact": "^0.6.9",
|
||||||
|
"@hcengineering/contact-resources": "^0.6.0",
|
||||||
"@hcengineering/view-resources": "^0.6.0",
|
"@hcengineering/view-resources": "^0.6.0",
|
||||||
"@hcengineering/text-editor": "^0.6.0",
|
"@hcengineering/text-editor": "^0.6.0",
|
||||||
"@hcengineering/panel": "^0.6.0",
|
"@hcengineering/panel": "^0.6.0",
|
||||||
@ -57,6 +58,8 @@
|
|||||||
"@hcengineering/workbench": "^0.6.2",
|
"@hcengineering/workbench": "^0.6.2",
|
||||||
"@hcengineering/attachment": "^0.6.1",
|
"@hcengineering/attachment": "^0.6.1",
|
||||||
"@hcengineering/chunter-resources": "^0.6.0",
|
"@hcengineering/chunter-resources": "^0.6.0",
|
||||||
"@hcengineering/workbench-resources": "^0.6.1"
|
"@hcengineering/workbench-resources": "^0.6.1",
|
||||||
|
"@hcengineering/activity-resources": "^0.6.1",
|
||||||
|
"@hcengineering/activity": "^0.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
export let shouldRender: boolean = true
|
export let shouldRender: boolean = true
|
||||||
export let onDateChange: (newDate: number | null) => void
|
export let onDateChange: (newDate: number | null) => void
|
||||||
export let kind: 'transparent' | 'primary' | 'link' | 'list' = 'primary'
|
export let kind: 'transparent' | 'primary' | 'link' | 'list' = 'primary'
|
||||||
|
export let editable: boolean = true
|
||||||
|
|
||||||
$: today = new Date(new Date(Date.now()).setHours(0, 0, 0, 0))
|
$: today = new Date(new Date(Date.now()).setHours(0, 0, 0, 0))
|
||||||
$: isOverdue = dateMs !== null && dateMs < today.getTime()
|
$: isOverdue = dateMs !== null && dateMs < today.getTime()
|
||||||
@ -33,7 +34,7 @@
|
|||||||
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
|
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
|
||||||
const newDate = event.detail
|
const newDate = event.detail
|
||||||
|
|
||||||
if (newDate === undefined || dateMs === newDate) {
|
if (newDate === undefined || dateMs === newDate || !editable) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +59,7 @@
|
|||||||
>
|
>
|
||||||
<DatePresenter
|
<DatePresenter
|
||||||
value={dateMs}
|
value={dateMs}
|
||||||
editable={true}
|
{editable}
|
||||||
shouldShowLabel={false}
|
shouldShowLabel={false}
|
||||||
icon={iconModifier}
|
icon={iconModifier}
|
||||||
{kind}
|
{kind}
|
||||||
@ -68,7 +69,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<DatePresenter
|
<DatePresenter
|
||||||
value={dateMs}
|
value={dateMs}
|
||||||
editable={true}
|
{editable}
|
||||||
shouldShowLabel={false}
|
shouldShowLabel={false}
|
||||||
icon={iconModifier}
|
icon={iconModifier}
|
||||||
{kind}
|
{kind}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
export let value: WithLookup<Issue>
|
export let value: WithLookup<Issue>
|
||||||
export let kind: 'transparent' | 'primary' | 'link' | 'list' = 'primary'
|
export let kind: 'transparent' | 'primary' | 'link' | 'list' = 'primary'
|
||||||
|
export let isEditable = true
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
||||||
@ -48,5 +49,6 @@
|
|||||||
dateMs={dueDateMs}
|
dateMs={dueDateMs}
|
||||||
shouldRender={shouldRenderPresenter}
|
shouldRender={shouldRenderPresenter}
|
||||||
onDateChange={handleDueDateChanged}
|
onDateChange={handleDueDateChanged}
|
||||||
|
editable={isEditable}
|
||||||
{kind}
|
{kind}
|
||||||
/>
|
/>
|
||||||
|
@ -16,12 +16,14 @@
|
|||||||
import { Ref, WithLookup } from '@hcengineering/core'
|
import { Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import type { Issue, Team } from '@hcengineering/tracker'
|
import type { Issue, Team } from '@hcengineering/tracker'
|
||||||
import { showPanel } from '@hcengineering/ui'
|
import { Icon, showPanel, tooltip } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Issue>
|
export let value: WithLookup<Issue>
|
||||||
export let disableClick = false
|
export let disableClick = false
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
|
export let withIcon = false
|
||||||
|
export let noUnderline = false
|
||||||
|
|
||||||
// Extra properties
|
// Extra properties
|
||||||
export let teams: Map<Ref<Team>, Team> | undefined = undefined
|
export let teams: Map<Ref<Team>, Team> | undefined = undefined
|
||||||
@ -56,18 +58,22 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span
|
<span class="issuePresenterRoot" class:noPointer={disableClick} class:noUnderline on:click={handleIssueEditorOpened}>
|
||||||
class="issuePresenterRoot"
|
{#if withIcon}
|
||||||
class:noPointer={disableClick}
|
<div class="mr-2" use:tooltip={{ label: tracker.string.Issue }}>
|
||||||
title={value?.title}
|
<Icon icon={tracker.icon.Issues} size={'small'} />
|
||||||
on:click={handleIssueEditorOpened}
|
</div>
|
||||||
>
|
{/if}
|
||||||
{title}
|
<span title={value?.title}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.issuePresenterRoot {
|
.issuePresenterRoot {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -82,10 +88,18 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&.noUnderline {
|
||||||
color: var(--caption-color);
|
color: var(--caption-color);
|
||||||
text-decoration: underline;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.noUnderline) {
|
||||||
|
&:hover {
|
||||||
|
color: var(--caption-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,13 @@
|
|||||||
export let value: Issue
|
export let value: Issue
|
||||||
export let shouldUseMargin: boolean = false
|
export let shouldUseMargin: boolean = false
|
||||||
export let showParent = true
|
export let showParent = true
|
||||||
|
export let onClick: (() => void) | undefined = undefined
|
||||||
|
|
||||||
function handleIssueEditorOpened () {
|
function handleIssueEditorOpened () {
|
||||||
|
if (onClick) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
|
||||||
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -86,6 +86,8 @@
|
|||||||
{ id: 'list', icon: view.icon.List, tooltip: view.string.List },
|
{ id: 'list', icon: view.icon.List, tooltip: view.string.List },
|
||||||
{ id: 'timeline', icon: view.icon.Timeline, tooltip: view.string.Timeline }
|
{ id: 'timeline', icon: view.icon.Timeline, tooltip: view.string.Timeline }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const retrieveMembers = (p: Project) => p.members
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fs-title flex-between header">
|
<div class="fs-title flex-between header">
|
||||||
@ -138,7 +140,16 @@
|
|||||||
presenter: tracker.component.LeadPresenter,
|
presenter: tracker.component.LeadPresenter,
|
||||||
props: { _class: tracker.class.Project, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
props: { _class: tracker.class.Project, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||||
},
|
},
|
||||||
{ key: '', presenter: tracker.component.ProjectMembersPresenter, props: { kind: 'link' } },
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: contact.component.MembersPresenter,
|
||||||
|
props: {
|
||||||
|
kind: 'link',
|
||||||
|
intlTitle: tracker.string.ProjectMembersTitle,
|
||||||
|
intlSearchPh: tracker.string.ProjectMembersSearchPlaceholder,
|
||||||
|
retrieveMembers
|
||||||
|
}
|
||||||
|
},
|
||||||
{ key: '', presenter: tracker.component.TargetDatePresenter },
|
{ key: '', presenter: tracker.component.TargetDatePresenter },
|
||||||
{ key: '', presenter: tracker.component.ProjectStatusPresenter }
|
{ key: '', presenter: tracker.component.ProjectStatusPresenter }
|
||||||
]}
|
]}
|
||||||
|
@ -15,11 +15,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { WithLookup } from '@hcengineering/core'
|
||||||
import { Project } from '@hcengineering/tracker'
|
import { Project } from '@hcengineering/tracker'
|
||||||
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
import { getCurrentLocation, Icon, navigate, tooltip } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Project>
|
export let value: WithLookup<Project>
|
||||||
|
export let withIcon = false
|
||||||
|
export let onClick: () => void | undefined
|
||||||
|
|
||||||
function navigateToProject () {
|
function navigateToProject () {
|
||||||
|
if (onClick) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
|
loc.path[4] = 'projects'
|
||||||
loc.path[5] = value._id
|
loc.path[5] = value._id
|
||||||
loc.path.length = 6
|
loc.path.length = 6
|
||||||
navigate(loc)
|
navigate(loc)
|
||||||
@ -28,11 +37,14 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span
|
<div class="flex" on:click={navigateToProject}>
|
||||||
title={value.label}
|
{#if withIcon}
|
||||||
class="cursor-pointer fs-bold caption-color overflow-label clear-mins"
|
<div class="mr-2" use:tooltip={{ label: tracker.string.Project }}>
|
||||||
on:click={navigateToProject}
|
<Icon icon={tracker.icon.Projects} size={'small'} />
|
||||||
>
|
</div>
|
||||||
{value.label}
|
{/if}
|
||||||
</span>
|
<span title={value.label} class="fs-bold cursor-pointer caption-color overflow-label clear-mins">
|
||||||
|
{value.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Doc, TxCUD } from '@hcengineering/core'
|
||||||
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
import { getObjectPresenter } from '@hcengineering/view-resources'
|
||||||
|
import { AttributeModel } from '@hcengineering/view'
|
||||||
|
|
||||||
|
export let value: TxCUD<Doc>
|
||||||
|
export let onNavigate: () => void | undefined
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
let presenter: AttributeModel | undefined
|
||||||
|
let doc: Doc | undefined
|
||||||
|
|
||||||
|
$: query.query(value.objectClass, { _id: value.objectId }, (res) => {
|
||||||
|
doc = res.shift()
|
||||||
|
})
|
||||||
|
|
||||||
|
$: getObjectPresenter(client, value.objectClass, { key: '' }).then((p) => {
|
||||||
|
presenter = p
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if doc && presenter}
|
||||||
|
<svelte:component this={presenter.presenter} value={doc} onClick={onNavigate} withIcon noUnderline />
|
||||||
|
{/if}
|
102
plugins/tracker-resources/src/components/scrums/NewScrum.svelte
Normal file
102
plugins/tracker-resources/src/components/scrums/NewScrum.svelte
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { Data, DateRangeMode, generateId, Ref } from '@hcengineering/core'
|
||||||
|
import { IntlString } from '@hcengineering/platform'
|
||||||
|
import { Card, getClient, SpaceSelector, UserBoxList } from '@hcengineering/presentation'
|
||||||
|
import { Scrum, Team } from '@hcengineering/tracker'
|
||||||
|
import { DateRangePresenter, EditBox } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import { StyledTextArea } from '@hcengineering/text-editor'
|
||||||
|
|
||||||
|
export let space: Ref<Team>
|
||||||
|
|
||||||
|
const objectId: Ref<Scrum> = generateId()
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const object: Partial<Data<Scrum>> = {
|
||||||
|
title: '' as IntlString,
|
||||||
|
description: '',
|
||||||
|
members: [],
|
||||||
|
attachments: 0,
|
||||||
|
scrumRecords: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let canSave = false
|
||||||
|
async function onSave () {
|
||||||
|
if (object.beginTime && object.endTime) {
|
||||||
|
await client.createDoc(tracker.class.Scrum, space, object as Data<Scrum>, objectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (
|
||||||
|
object.endTime &&
|
||||||
|
object.beginTime &&
|
||||||
|
object.endTime - object.beginTime > 0 &&
|
||||||
|
object.title !== '' &&
|
||||||
|
object.members?.length !== 0
|
||||||
|
) {
|
||||||
|
canSave = true
|
||||||
|
} else {
|
||||||
|
canSave = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={tracker.string.NewScrum}
|
||||||
|
okLabel={tracker.string.CreateScrum}
|
||||||
|
{canSave}
|
||||||
|
okAction={onSave}
|
||||||
|
on:close={() => dispatch('close')}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
<SpaceSelector _class={tracker.class.Team} label={tracker.string.Team} bind:space />
|
||||||
|
</svelte:fragment>
|
||||||
|
<EditBox bind:value={object.title} placeholder={tracker.string.ScrumTitlePlaceholder} kind={'large-style'} focus />
|
||||||
|
<StyledTextArea
|
||||||
|
bind:content={object.description}
|
||||||
|
placeholder={tracker.string.ScrumDescriptionPlaceholder}
|
||||||
|
emphasized
|
||||||
|
/>
|
||||||
|
<svelte:fragment slot="pool">
|
||||||
|
<UserBoxList bind:items={object.members} label={tracker.string.ScrumMembersSearchPlaceholder} />
|
||||||
|
<DateRangePresenter
|
||||||
|
value={object.beginTime}
|
||||||
|
labelNull={tracker.string.ScrumBeginTime}
|
||||||
|
mode={DateRangeMode.TIME}
|
||||||
|
on:change={(res) => {
|
||||||
|
if (res.detail !== undefined && res.detail !== null) {
|
||||||
|
object.beginTime = res.detail
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
editable
|
||||||
|
/>
|
||||||
|
<DateRangePresenter
|
||||||
|
value={object.endTime}
|
||||||
|
labelNull={tracker.string.ScrumEndTime}
|
||||||
|
mode={DateRangeMode.TIME}
|
||||||
|
on:change={(res) => {
|
||||||
|
if (res.detail !== undefined && res.detail !== null) {
|
||||||
|
object.endTime = res.detail
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
editable
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Card>
|
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { Button, IconStart, IconStop } from '@hcengineering/ui'
|
||||||
|
import { handleRecordingScrum } from '../..'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let scrum: Scrum
|
||||||
|
export let activeScrumRecord: ScrumRecord | undefined
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
$: isRecording = scrum._id === activeScrumRecord?.attachedTo
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={isRecording ? IconStop : IconStart}
|
||||||
|
label={isRecording ? tracker.string.StopRecord : tracker.string.StartRecord}
|
||||||
|
kind={'primary'}
|
||||||
|
on:click={() => handleRecordingScrum(client, scrum, activeScrumRecord)}
|
||||||
|
/>
|
@ -0,0 +1,38 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { translate } from '@hcengineering/platform'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { Button } from '@hcengineering/ui'
|
||||||
|
import { handleRecordingScrum } from '../..'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let value: Scrum
|
||||||
|
export let activeScrumRecord: ScrumRecord | undefined
|
||||||
|
|
||||||
|
let title: string
|
||||||
|
const client = getClient()
|
||||||
|
$: isRecording = value._id === activeScrumRecord?.attachedTo
|
||||||
|
$: translate(isRecording ? tracker.string.StopRecord : tracker.string.StartRecord, {}).then((res) => (title = res))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
kind="link"
|
||||||
|
justify="center"
|
||||||
|
{title}
|
||||||
|
icon={isRecording ? tracker.icon.Stop : tracker.icon.Start}
|
||||||
|
on:click={async () => handleRecordingScrum(client, value, activeScrumRecord)}
|
||||||
|
/>
|
@ -0,0 +1,59 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { DateRangeMode } from '@hcengineering/core'
|
||||||
|
import { getClient } from '@hcengineering/presentation'
|
||||||
|
import { Scrum } from '@hcengineering/tracker'
|
||||||
|
import { DateRangePresenter } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let value: Scrum
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
$: beginTime = value.beginTime
|
||||||
|
$: endTime = value.endTime
|
||||||
|
|
||||||
|
const updateObject = (fields: Partial<Scrum> | undefined) => {
|
||||||
|
if (fields) {
|
||||||
|
client.update(value, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DateRangePresenter
|
||||||
|
value={beginTime}
|
||||||
|
mode={DateRangeMode.TIME}
|
||||||
|
labelNull={tracker.string.ScrumBeginTime}
|
||||||
|
on:change={(res) => {
|
||||||
|
if (res.detail !== null && res.detail !== undefined && res.detail < endTime) {
|
||||||
|
updateObject({ beginTime: res.detail })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
noShift
|
||||||
|
editable
|
||||||
|
/>
|
||||||
|
<DateRangePresenter
|
||||||
|
value={endTime}
|
||||||
|
mode={DateRangeMode.TIME}
|
||||||
|
labelNull={tracker.string.ScrumEndTime}
|
||||||
|
on:change={(res) => {
|
||||||
|
if (res.detail !== null && res.detail !== undefined && res.detail > beginTime) {
|
||||||
|
updateObject({ endTime: res.detail })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
noShift
|
||||||
|
editable
|
||||||
|
/>
|
@ -0,0 +1,48 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { getClient } from '@hcengineering/presentation'
|
||||||
|
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||||
|
import type { Scrum } from '@hcengineering/tracker'
|
||||||
|
import { EditBox } from '@hcengineering/ui'
|
||||||
|
import { DocAttributeBar } from '@hcengineering/view-resources'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let scrum: Scrum
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
async function change (field: string, value: any) {
|
||||||
|
await client.update(scrum, { [field]: value })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="popupPanel-body__aside flex shown">
|
||||||
|
<div class="p-4 w-60 left-divider">
|
||||||
|
<div class="fs-title text-xl">
|
||||||
|
<EditBox bind:value={scrum.title} on:change={() => scrum.title && change('title', scrum.title)} />
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<StyledTextBox
|
||||||
|
alwaysEdit
|
||||||
|
showButtons={false}
|
||||||
|
placeholder={tracker.string.Description}
|
||||||
|
content={scrum.description ?? ''}
|
||||||
|
on:value={(evt) => change('description', evt.detail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DocAttributeBar object={scrum} ignoreKeys={['title', 'description']} />
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,71 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 {
|
||||||
|
Button,
|
||||||
|
deviceOptionsStore as deviceInfo,
|
||||||
|
getCurrentLocation,
|
||||||
|
Icon,
|
||||||
|
navigate,
|
||||||
|
showPopup
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import ScrumPopup from './ScrumPopup.svelte'
|
||||||
|
import Expanded from '../icons/Expanded.svelte'
|
||||||
|
import { SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
|
import { Scrum } from '@hcengineering/tracker'
|
||||||
|
|
||||||
|
export let scrum: WithLookup<Scrum>
|
||||||
|
|
||||||
|
let container: HTMLElement
|
||||||
|
|
||||||
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
const handleSelectScrum = (evt: MouseEvent): void => {
|
||||||
|
showPopup(
|
||||||
|
ScrumPopup,
|
||||||
|
{
|
||||||
|
_class: tracker.class.Scrum,
|
||||||
|
query: { space: scrum.space },
|
||||||
|
options: { sort: { beginTime: SortingOrder.Ascending } }
|
||||||
|
},
|
||||||
|
container,
|
||||||
|
(value) => {
|
||||||
|
if (value != null) {
|
||||||
|
const loc = getCurrentLocation()
|
||||||
|
loc.path[5] = value._id
|
||||||
|
navigate(loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
|
<div class:ac-header-full={!twoRows} class:flex-between={twoRows}>
|
||||||
|
<div bind:this={container} class="ac-header__wrap-title mr-3">
|
||||||
|
<Button size={'small'} kind={'link'} on:click={handleSelectScrum}>
|
||||||
|
<svelte:fragment slot="content">
|
||||||
|
<div class="ac-header__icon">
|
||||||
|
<Icon icon={tracker.icon.Scrum} size={'small'} />
|
||||||
|
</div>
|
||||||
|
<span class="ac-header__title mr-1">{scrum.title}</span>
|
||||||
|
<Icon icon={Expanded} size={'small'} />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot name="options" />
|
||||||
|
</div>
|
@ -0,0 +1,43 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 Anticrm Platform Contributors.
|
||||||
|
//
|
||||||
|
// 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 type { DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||||
|
import { ObjectPopup } from '@hcengineering/presentation'
|
||||||
|
import { Scrum } from '@hcengineering/tracker'
|
||||||
|
import ScrumTitle from './ScrumTitle.svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let selected: Ref<Scrum> | undefined = undefined
|
||||||
|
export let query: DocumentQuery<Scrum> = {}
|
||||||
|
export let options: FindOptions<Scrum> = {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ObjectPopup
|
||||||
|
_class={tracker.class.Scrum}
|
||||||
|
{selected}
|
||||||
|
bind:docQuery={query}
|
||||||
|
{options}
|
||||||
|
searchField="title"
|
||||||
|
multiSelect={false}
|
||||||
|
allowDeselect={false}
|
||||||
|
closeAfterSelect
|
||||||
|
shadows
|
||||||
|
on:update
|
||||||
|
on:close
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="item" let:item={scrum}>
|
||||||
|
<ScrumTitle value={scrum} />
|
||||||
|
</svelte:fragment>
|
||||||
|
</ObjectPopup>
|
@ -0,0 +1,44 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { Scrum } from '@hcengineering/tracker'
|
||||||
|
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
||||||
|
|
||||||
|
export let value: Scrum
|
||||||
|
function navigateToScrum () {
|
||||||
|
const loc = getCurrentLocation()
|
||||||
|
loc.path[5] = value._id
|
||||||
|
loc.path.length = 6
|
||||||
|
navigate(loc)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="flex-presenter flex-grow" on:click={navigateToScrum}>
|
||||||
|
<span title={value.title} class="scrumLabel flex-grow">{value.title}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.scrumLabel {
|
||||||
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,54 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { Button, deviceOptionsStore as deviceInfo, Icon, showPopup } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import Expanded from '../icons/Expanded.svelte'
|
||||||
|
import { WithLookup } from '@hcengineering/core'
|
||||||
|
import { ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import ScrumRecordPopup from './ScrumRecordPopup.svelte'
|
||||||
|
import ScrumRecordTitlePresenter from './ScrumRecordTitlePresenter.svelte'
|
||||||
|
|
||||||
|
export let scrumRecord: WithLookup<ScrumRecord>
|
||||||
|
|
||||||
|
let container: HTMLElement
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
const handleSelectScrumRecord = (evt: MouseEvent): void => {
|
||||||
|
showPopup(ScrumRecordPopup, { query: { attachedTo: scrumRecord.attachedTo } }, container, (value) => {
|
||||||
|
if (value != null) {
|
||||||
|
scrumRecord = value
|
||||||
|
dispatch('scrumRecord', scrumRecord._id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
|
<div class:ac-header-full={!twoRows} class:flex-between={twoRows}>
|
||||||
|
<div bind:this={container} class="ac-header__wrap-title mr-3">
|
||||||
|
<Button size={'small'} kind={'link'} on:click={handleSelectScrumRecord}>
|
||||||
|
<svelte:fragment slot="content">
|
||||||
|
<ScrumRecordTitlePresenter value={scrumRecord} />
|
||||||
|
<Icon icon={Expanded} size={'small'} />
|
||||||
|
</svelte:fragment>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot name="options" />
|
||||||
|
</div>
|
@ -0,0 +1,68 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { DateRangeMode, WithLookup } from '@hcengineering/core'
|
||||||
|
import { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { DateRangePresenter, Label } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import { EmployeeRefPresenter } from '@hcengineering/contact-resources'
|
||||||
|
|
||||||
|
export let scrumRecord: WithLookup<ScrumRecord>
|
||||||
|
export let scrum: Scrum
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<span class="label fs-bold">
|
||||||
|
<Label label={tracker.string.ScrumRecorder} />
|
||||||
|
</span>
|
||||||
|
<EmployeeRefPresenter value={scrumRecord.$lookup?.scrumRecorder?.employee} />
|
||||||
|
|
||||||
|
<span class="label fs-bold">
|
||||||
|
<Label label={tracker.string.ScrumBeginTime} />
|
||||||
|
</span>
|
||||||
|
<DateRangePresenter value={scrumRecord.startTs} mode={DateRangeMode.DATETIME} kind="link" editable={false} />
|
||||||
|
|
||||||
|
{#if scrumRecord.endTs}
|
||||||
|
<span class="label fs-bold">
|
||||||
|
<Label label={tracker.string.ScrumEndTime} />
|
||||||
|
</span>
|
||||||
|
<DateRangePresenter value={scrumRecord.endTs} mode={DateRangeMode.DATETIME} kind="link" editable={false} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<span class="label fs-bold">
|
||||||
|
<Label label={tracker.string.Scrum} />
|
||||||
|
</span>
|
||||||
|
<span class="fs-bold scrumTitle">
|
||||||
|
{scrum.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1.5fr;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
height: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrumTitle {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,114 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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, { AttachedDoc, Doc, SortingOrder, TxCollectionCUD, TxCUD } from '@hcengineering/core'
|
||||||
|
import { Issue } from '@hcengineering/tracker'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import { List } from '@hcengineering/view-resources'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
import { groupBy } from '@hcengineering/view-resources/src/utils'
|
||||||
|
import ChangedObjectPresenter from './ChangedObjectPresenter.svelte'
|
||||||
|
|
||||||
|
export let txes: TxCUD<Doc>[]
|
||||||
|
export let onNavigate: () => void
|
||||||
|
|
||||||
|
const TRACKED_OBJECTS = [
|
||||||
|
tracker.class.Issue,
|
||||||
|
tracker.class.IssueTemplate,
|
||||||
|
tracker.class.Project,
|
||||||
|
tracker.class.Sprint
|
||||||
|
] as const
|
||||||
|
|
||||||
|
let changedObjectTxes: TxCUD<Doc>[] = []
|
||||||
|
|
||||||
|
// Drop RemoveDoc Txes and filter by supported tracked objects
|
||||||
|
$: filteredTxesCU = txes
|
||||||
|
.filter((tx) => TRACKED_OBJECTS.includes(tx.objectClass))
|
||||||
|
.filter((tx) => {
|
||||||
|
if (tx.objectClass === tracker.class.Issue) {
|
||||||
|
const issueTx = tx as TxCollectionCUD<Issue, AttachedDoc>
|
||||||
|
return issueTx.tx.objectClass !== tracker.class.Issue || issueTx.tx._class !== core.class.TxRemoveDoc
|
||||||
|
}
|
||||||
|
return tx._class !== core.class.TxRemoveDoc
|
||||||
|
})
|
||||||
|
|
||||||
|
// Convert Issue txes to common model
|
||||||
|
$: objectTxes = filteredTxesCU.map((tx) => {
|
||||||
|
const objectTx = { ...tx }
|
||||||
|
|
||||||
|
if (tx.objectClass === tracker.class.Issue) {
|
||||||
|
const issueTx = tx as TxCollectionCUD<Issue, AttachedDoc>
|
||||||
|
if (issueTx.tx.objectClass === tracker.class.Issue) {
|
||||||
|
objectTx.objectId = issueTx.tx.objectId
|
||||||
|
objectTx._class = issueTx.tx._class
|
||||||
|
} else {
|
||||||
|
objectTx._class = core.class.TxUpdateDoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectTx
|
||||||
|
})
|
||||||
|
|
||||||
|
// Retrieve single txes for changed objects: TxCreateDoc if it exist for object, else last TxUpdateDoc
|
||||||
|
$: {
|
||||||
|
changedObjectTxes = []
|
||||||
|
|
||||||
|
const txesByObjectId = groupBy(objectTxes, 'objectId')
|
||||||
|
|
||||||
|
Object.values(txesByObjectId).forEach((objectTxes) => {
|
||||||
|
let objectTx: TxCUD<Doc> | undefined
|
||||||
|
|
||||||
|
objectTxes.forEach((tx) => {
|
||||||
|
const currentTx = tx as TxCUD<Doc>
|
||||||
|
|
||||||
|
if (
|
||||||
|
!objectTx ||
|
||||||
|
currentTx._class === core.class.TxCreateDoc ||
|
||||||
|
(objectTx._class === core.class.TxUpdateDoc && objectTx.modifiedOn < currentTx.modifiedOn)
|
||||||
|
) {
|
||||||
|
objectTx = currentTx
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (objectTx) {
|
||||||
|
changedObjectTxes.push(objectTx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
changedObjectTxes = changedObjectTxes.sort((leftObjectTx, rightObjectTx) =>
|
||||||
|
leftObjectTx.objectClass.localeCompare(rightObjectTx.objectClass)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<List
|
||||||
|
_class={core.class.TxCUD}
|
||||||
|
documents={changedObjectTxes}
|
||||||
|
config={[
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: ChangedObjectPresenter,
|
||||||
|
props: { onNavigate }
|
||||||
|
},
|
||||||
|
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||||
|
{
|
||||||
|
key: 'modifiedOn',
|
||||||
|
presenter: tracker.component.ModificationDatePresenter,
|
||||||
|
props: {}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
viewOptions={{ orderBy: ['modifiedOn', SortingOrder.Descending], groupBy: [] }}
|
||||||
|
disableHeader
|
||||||
|
/>
|
@ -0,0 +1,149 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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, { Class, Doc, Ref, SortingOrder, TxCUD, WithLookup } from '@hcengineering/core'
|
||||||
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
|
import type { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { UpDownNavigator } from '@hcengineering/view-resources'
|
||||||
|
import { Panel } from '@hcengineering/panel'
|
||||||
|
import { Button, closePanel, TabItem, TabList } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import { handleRecordingScrum } from '../..'
|
||||||
|
import ScrumRecordInfo from './ScrumRecordInfo.svelte'
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
|
import ScrumRecordTimeSpend from './ScrumRecordTimeSpend.svelte'
|
||||||
|
import ScrumRecordObjects from './ScrumRecordObjects.svelte'
|
||||||
|
import { scrumRecordTitleMap, ScrumRecordViewMode } from '../../utils'
|
||||||
|
|
||||||
|
export let _id: Ref<ScrumRecord>
|
||||||
|
export let _class: Ref<Class<ScrumRecord>>
|
||||||
|
|
||||||
|
const scrumRecordQuery = createQuery()
|
||||||
|
const client = getClient()
|
||||||
|
const txQuery = createQuery()
|
||||||
|
|
||||||
|
const modeList: TabItem[] = Object.entries(scrumRecordTitleMap).map(([id, labelIntl]) => ({
|
||||||
|
id,
|
||||||
|
labelIntl,
|
||||||
|
action: () => handleViewModeChanged(id as ScrumRecordViewMode)
|
||||||
|
}))
|
||||||
|
|
||||||
|
let scrumRecord: WithLookup<ScrumRecord> | undefined
|
||||||
|
let scrum: Scrum | undefined
|
||||||
|
let isRecording = false
|
||||||
|
let txes: TxCUD<Doc>[] = []
|
||||||
|
|
||||||
|
let mode: ScrumRecordViewMode = 'timeReports'
|
||||||
|
|
||||||
|
const onNavigate = () => closePanel()
|
||||||
|
const handleViewModeChanged = (newMode: ScrumRecordViewMode) => {
|
||||||
|
if (newMode === undefined || newMode === mode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = newMode
|
||||||
|
}
|
||||||
|
|
||||||
|
$: _class &&
|
||||||
|
_id &&
|
||||||
|
scrumRecordQuery.query(
|
||||||
|
_class,
|
||||||
|
{ _id },
|
||||||
|
(result) => {
|
||||||
|
scrumRecord = result.shift()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
attachedTo: tracker.class.Scrum,
|
||||||
|
scrumRecorder: contact.class.EmployeeAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
$: scrum = scrumRecord?.$lookup?.attachedTo
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (scrumRecord?.startTs && !scrumRecord.endTs && scrumRecord.scrumRecorder) {
|
||||||
|
isRecording = true
|
||||||
|
} else {
|
||||||
|
isRecording = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: scrumRecord &&
|
||||||
|
txQuery.query(
|
||||||
|
core.class.TxCUD,
|
||||||
|
{
|
||||||
|
modifiedOn: { $gte: scrumRecord.startTs, ...(scrumRecord.endTs ? { $lte: scrumRecord.endTs } : {}) },
|
||||||
|
modifiedBy: scrumRecord.scrumRecorder
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
txes = res
|
||||||
|
},
|
||||||
|
{ sort: { modifiedOn: SortingOrder.Ascending } }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if scrumRecord && scrum}
|
||||||
|
<Panel object={scrumRecord} isUtils={isRecording} isHeader={false} on:close>
|
||||||
|
<svelte:fragment slot="navigator">
|
||||||
|
<UpDownNavigator element={scrumRecord} />
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="title">
|
||||||
|
<span class="fs-title select-text-i">
|
||||||
|
{scrumRecord.label}
|
||||||
|
</span>
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="utils">
|
||||||
|
{#if isRecording}
|
||||||
|
<Button
|
||||||
|
kind="transparent"
|
||||||
|
showTooltip={{ label: tracker.string.StopRecord }}
|
||||||
|
icon={tracker.icon.Stop}
|
||||||
|
on:click={() => scrum && handleRecordingScrum(client, scrum, scrumRecord)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="custom-attributes">
|
||||||
|
<ScrumRecordInfo {scrumRecord} {scrum} />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<div class="itemsContainer">
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<TabList
|
||||||
|
items={modeList}
|
||||||
|
selected={mode}
|
||||||
|
on:select={(result) => {
|
||||||
|
if (result.detail !== undefined && result.detail.action) result.detail.action()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if mode === 'timeReports'}
|
||||||
|
<ScrumRecordTimeSpend {txes} members={scrum.members} {onNavigate} />
|
||||||
|
{/if}
|
||||||
|
{#if mode === 'objects'}
|
||||||
|
<ScrumRecordObjects {txes} {onNavigate} />
|
||||||
|
{/if}
|
||||||
|
</Panel>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.itemsContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.65rem 0.75rem 0.65rem 2.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,43 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { showPanel } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
|
export let value: ScrumRecord
|
||||||
|
|
||||||
|
function handleOpenPanel () {
|
||||||
|
showPanel(tracker.component.ScrumRecordPanel, value._id, value._class, 'content')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="flex-presenter flex-grow" on:click={handleOpenPanel}>
|
||||||
|
<span title={value.label} class="scrumRecordLabel flex-grow">{value.label}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.scrumRecordLabel {
|
||||||
|
display: block;
|
||||||
|
min-width: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,194 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { createQuery } from '@hcengineering/presentation'
|
||||||
|
import core, { Doc, Ref, SortingOrder, TxCollectionCUD, TxCreateDoc, TxCUD, TxUpdateDoc } from '@hcengineering/core'
|
||||||
|
import { Issue, TimeSpendReport } from '@hcengineering/tracker'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import { Employee } from '@hcengineering/contact'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
import { List } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
|
type TimeSpendByEmployee = { [key: Ref<Employee>]: number | undefined }
|
||||||
|
type TimeSpendByIssue = { [key: Ref<Issue>]: TimeSpendByEmployee | undefined }
|
||||||
|
type TimeSpendInfo = {
|
||||||
|
issueId: Ref<Issue>
|
||||||
|
value: number
|
||||||
|
employee: Ref<Employee>
|
||||||
|
}
|
||||||
|
|
||||||
|
export let members: Ref<Employee>[]
|
||||||
|
export let txes: TxCUD<Doc>[] = []
|
||||||
|
export let onNavigate: () => void
|
||||||
|
|
||||||
|
const issuesQuery = createQuery()
|
||||||
|
|
||||||
|
let timeSpendInfoByIssue: TimeSpendByIssue = {}
|
||||||
|
let viewableIssues: Issue[] = []
|
||||||
|
|
||||||
|
$: issueTxes = txes.filter((tx) => tx.objectClass === tracker.class.Issue)
|
||||||
|
$: {
|
||||||
|
timeSpendInfoByIssue = {}
|
||||||
|
const timeSpendRecords: { [key: Ref<TimeSpendReport>]: TimeSpendInfo | undefined } = {}
|
||||||
|
|
||||||
|
const timeSpendTxes = (issueTxes as TxCollectionCUD<Issue, TimeSpendReport>[]).filter(
|
||||||
|
(tx) => tx.tx.objectClass === tracker.class.TimeSpendReport
|
||||||
|
)
|
||||||
|
|
||||||
|
const addNewTimeSpend = (
|
||||||
|
issueId: Ref<Issue>,
|
||||||
|
timeSpendId: Ref<TimeSpendReport>,
|
||||||
|
employee?: Ref<Employee> | null,
|
||||||
|
newValue?: number
|
||||||
|
) => {
|
||||||
|
if (newValue && employee && members.includes(employee)) {
|
||||||
|
if (!(issueId in timeSpendInfoByIssue)) {
|
||||||
|
timeSpendInfoByIssue[issueId] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordedValue = timeSpendInfoByIssue[issueId]![employee] ?? 0
|
||||||
|
|
||||||
|
timeSpendInfoByIssue[issueId]![employee] = newValue + recordedValue
|
||||||
|
timeSpendRecords[timeSpendId] = {
|
||||||
|
issueId,
|
||||||
|
employee,
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeSpendTxes
|
||||||
|
.filter((tx) => tx.tx._class === core.class.TxCreateDoc)
|
||||||
|
.forEach((tx) => {
|
||||||
|
const timeSpendTxCreate = tx.tx as TxCreateDoc<TimeSpendReport>
|
||||||
|
const employee = timeSpendTxCreate.attributes.employee
|
||||||
|
const newValue = timeSpendTxCreate.attributes.value
|
||||||
|
|
||||||
|
addNewTimeSpend(tx.objectId, tx.tx.objectId, employee, newValue)
|
||||||
|
console.log(JSON.stringify({ newValue, employee }))
|
||||||
|
console.log('TX:', JSON.stringify(tx.tx))
|
||||||
|
})
|
||||||
|
|
||||||
|
timeSpendTxes
|
||||||
|
.filter((tx) => tx.tx._class === core.class.TxUpdateDoc)
|
||||||
|
.forEach((tx) => {
|
||||||
|
const timeSpendTxUpdate = tx.tx as TxUpdateDoc<TimeSpendReport>
|
||||||
|
const employee = timeSpendTxUpdate.operations.employee
|
||||||
|
const value = timeSpendTxUpdate.operations.value
|
||||||
|
const timeSpendId = timeSpendTxUpdate.objectId
|
||||||
|
const recordedTimeSpend = timeSpendRecords[timeSpendId]
|
||||||
|
|
||||||
|
if (employee || value) {
|
||||||
|
if (recordedTimeSpend) {
|
||||||
|
const recordedValueByEmployee =
|
||||||
|
timeSpendInfoByIssue[recordedTimeSpend.issueId]![recordedTimeSpend.employee]!
|
||||||
|
|
||||||
|
const newValue = recordedValueByEmployee - recordedTimeSpend.value
|
||||||
|
if (newValue === 0) {
|
||||||
|
delete timeSpendInfoByIssue[recordedTimeSpend.issueId]![recordedTimeSpend.employee]
|
||||||
|
} else {
|
||||||
|
timeSpendInfoByIssue[recordedTimeSpend.issueId]![recordedTimeSpend.employee] = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordingValue = value ?? recordedTimeSpend?.value
|
||||||
|
const recordingEmployee = employee ?? recordedTimeSpend?.employee
|
||||||
|
console.log(JSON.stringify({ recordingValue, recordingEmployee }))
|
||||||
|
console.log('TX:', JSON.stringify(tx.tx))
|
||||||
|
|
||||||
|
addNewTimeSpend(tx.objectId, timeSpendId, recordingEmployee, recordingValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update reported time and assignee for tracked issues according to tracked TimeSpendReports
|
||||||
|
$: issuesQuery.query(
|
||||||
|
tracker.class.Issue,
|
||||||
|
{
|
||||||
|
_id: { $in: Object.keys(timeSpendInfoByIssue) as Ref<Issue>[] }
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
const issues = res
|
||||||
|
viewableIssues = []
|
||||||
|
|
||||||
|
for (const [issueId, timeSpendInfo] of Object.entries(timeSpendInfoByIssue)) {
|
||||||
|
const currentIssue = issues.find((issue) => issue._id === issueId)
|
||||||
|
|
||||||
|
if (!timeSpendInfo || !currentIssue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const [employeeId, reportedTime] of Object.entries(timeSpendInfo)) {
|
||||||
|
viewableIssues.push({ ...currentIssue, reportedTime: reportedTime!, assignee: employeeId as Ref<Employee> })
|
||||||
|
}
|
||||||
|
|
||||||
|
viewableIssues = viewableIssues.sort(
|
||||||
|
(issueLeft, issueRight) => issueRight.reportedTime - issueLeft.reportedTime
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: { priority: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<List
|
||||||
|
_class={tracker.class.Issue}
|
||||||
|
documents={viewableIssues}
|
||||||
|
config={[
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.PriorityEditor,
|
||||||
|
props: { kind: 'list', size: 'small', isEditable: false }
|
||||||
|
},
|
||||||
|
{ key: '', presenter: tracker.component.IssuePresenter, props: { onClick: onNavigate } },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.StatusEditor,
|
||||||
|
props: { kind: 'list', size: 'small', justify: 'center', isEditable: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.TitlePresenter,
|
||||||
|
props: { shouldUseMargin: true, showParent: false, onClick: onNavigate }
|
||||||
|
},
|
||||||
|
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||||
|
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||||
|
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list', isEditable: false } },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.SprintEditor,
|
||||||
|
props: {
|
||||||
|
kind: 'list',
|
||||||
|
size: 'small',
|
||||||
|
shape: 'round',
|
||||||
|
shouldShowPlaceholder: false,
|
||||||
|
excludeByKey: 'sprint',
|
||||||
|
isEditable: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.EstimationEditor,
|
||||||
|
props: { kind: 'list', size: 'small', isEditable: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'modifiedOn',
|
||||||
|
presenter: tracker.component.ModificationDatePresenter,
|
||||||
|
props: {}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
viewOptions={{ orderBy: ['modifiedOn', SortingOrder.Descending], groupBy: ['assignee'] }}
|
||||||
|
/>
|
@ -0,0 +1,74 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { SortingOrder } from '@hcengineering/core'
|
||||||
|
import { Scrum, ScrumRecord } from '@hcengineering/tracker'
|
||||||
|
import { Button, Icon, IconDetails, IconDetailsFilled } from '@hcengineering/ui'
|
||||||
|
import { ActionContext, List } from '@hcengineering/view-resources'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import RecordScrumButton from './RecordScrumButton.svelte'
|
||||||
|
import ScrumEditor from './ScrumEditor.svelte'
|
||||||
|
import ScrumHeader from './ScrumHeader.svelte'
|
||||||
|
import ScrumRecordPresenter from './ScrumRecordPresenter.svelte'
|
||||||
|
|
||||||
|
export let scrum: Scrum
|
||||||
|
export let activeScrumRecord: ScrumRecord | undefined
|
||||||
|
|
||||||
|
let asideShown = true
|
||||||
|
|
||||||
|
$: query = { space: scrum.space, attachedTo: scrum._id }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionContext
|
||||||
|
context={{
|
||||||
|
mode: 'browser'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrumHeader {scrum}>
|
||||||
|
<svelte:fragment slot="options">
|
||||||
|
<RecordScrumButton {scrum} {activeScrumRecord} />
|
||||||
|
<Button
|
||||||
|
icon={asideShown ? IconDetailsFilled : IconDetails}
|
||||||
|
kind={'transparent'}
|
||||||
|
size={'medium'}
|
||||||
|
selected={asideShown}
|
||||||
|
on:click={() => (asideShown = !asideShown)}
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
</ScrumHeader>
|
||||||
|
<div class="top-divider flex w-full h-full clear-mins">
|
||||||
|
<List
|
||||||
|
_class={tracker.class.ScrumRecord}
|
||||||
|
space={scrum.space}
|
||||||
|
{query}
|
||||||
|
viewOptions={{
|
||||||
|
orderBy: ['modifiedOn', SortingOrder.Descending],
|
||||||
|
groupBy: []
|
||||||
|
}}
|
||||||
|
config={[
|
||||||
|
{ key: '', presenter: Icon, props: { icon: tracker.icon.Scrum, size: 'small' } },
|
||||||
|
{ key: '', presenter: ScrumRecordPresenter },
|
||||||
|
{
|
||||||
|
key: 'modifiedOn',
|
||||||
|
presenter: tracker.component.ModificationDatePresenter
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
disableHeader
|
||||||
|
/>
|
||||||
|
{#if asideShown}
|
||||||
|
<ScrumEditor bind:scrum />
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -0,0 +1,41 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { Scrum } from '@hcengineering/tracker'
|
||||||
|
import { Icon } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
export let value: Scrum | undefined
|
||||||
|
|
||||||
|
const getMinutes = (date: Date) => {
|
||||||
|
const currentMinutes = date.getMinutes()
|
||||||
|
return Math.floor(currentMinutes / 10) > 0 ? currentMinutes : `0${currentMinutes}`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value}
|
||||||
|
{@const start = new Date(value.beginTime)}
|
||||||
|
{@const end = new Date(value.endTime)}
|
||||||
|
<span class="overflow-label flex-row-center flex-grow">
|
||||||
|
<Icon icon={tracker.icon.Scrum} size={'small'} />
|
||||||
|
<div class="ml-2 mr-2">
|
||||||
|
{value.title}
|
||||||
|
</div>
|
||||||
|
<span class="flex flex-grow justify-end">
|
||||||
|
{`${start.getHours()}:${getMinutes(start)}`}
|
||||||
|
-
|
||||||
|
{`${end.getHours()}:${getMinutes(end)}`}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{/if}
|
@ -0,0 +1,70 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 { createQuery } from '@hcengineering/presentation'
|
||||||
|
import { Scrum, ScrumRecord, Team } from '@hcengineering/tracker'
|
||||||
|
import { closePopup, closeTooltip, location } from '@hcengineering/ui'
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import ScrumRecordsView from './ScrumRecordsView.svelte'
|
||||||
|
import ScrumsView from './ScrumsView.svelte'
|
||||||
|
|
||||||
|
export let currentSpace: Ref<Team>
|
||||||
|
|
||||||
|
let scrumId: Ref<Scrum> | undefined
|
||||||
|
let scrum: Scrum | undefined
|
||||||
|
let activeScrumRecord: ScrumRecord | undefined
|
||||||
|
|
||||||
|
const activeRecordQuery = createQuery()
|
||||||
|
const scrumQuery = createQuery()
|
||||||
|
|
||||||
|
onDestroy(
|
||||||
|
location.subscribe(async (loc) => {
|
||||||
|
closeTooltip()
|
||||||
|
closePopup()
|
||||||
|
|
||||||
|
scrumId = loc.path[5] as Ref<Scrum>
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
$: if (scrumId) {
|
||||||
|
scrumQuery.query(tracker.class.Scrum, { _id: scrumId }, (res) => {
|
||||||
|
scrum = res.shift()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
scrumQuery.unsubscribe()
|
||||||
|
scrum = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
$: activeRecordQuery.query(
|
||||||
|
tracker.class.ScrumRecord,
|
||||||
|
{
|
||||||
|
space: currentSpace,
|
||||||
|
scrumRecorder: { $exists: true },
|
||||||
|
startTs: { $exists: true },
|
||||||
|
endTs: { $exists: false }
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
activeScrumRecord = result.shift()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if scrum}
|
||||||
|
<ScrumRecordsView {activeScrumRecord} {scrum} />
|
||||||
|
{:else}
|
||||||
|
<ScrumsView {activeScrumRecord} {currentSpace} />
|
||||||
|
{/if}
|
@ -0,0 +1,81 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2023 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 contact from '@hcengineering/contact'
|
||||||
|
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||||
|
import { ScrumRecord, Sprint, Team } from '@hcengineering/tracker'
|
||||||
|
import { Button, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||||
|
import { ActionContext, List } from '@hcengineering/view-resources'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import NewScrum from './NewScrum.svelte'
|
||||||
|
import RecordScrumPresenter from './RecordScrumPresenter.svelte'
|
||||||
|
import ScrumDatePresenter from './ScrumDatePresenter.svelte'
|
||||||
|
import ScrumPresenter from './ScrumPresenter.svelte'
|
||||||
|
|
||||||
|
export let currentSpace: Ref<Team>
|
||||||
|
export let activeScrumRecord: ScrumRecord | undefined
|
||||||
|
|
||||||
|
const showCreateDialog = async () => {
|
||||||
|
showPopup(NewScrum, { space: currentSpace, targetElement: null }, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const retrieveMembers = (s: Sprint) => s.members
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionContext
|
||||||
|
context={{
|
||||||
|
mode: 'browser'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="fs-title flex-between header">
|
||||||
|
<Label label={tracker.string.Scrums} />
|
||||||
|
<div class="flex-between flex-gap-2">
|
||||||
|
<Button size="small" icon={IconAdd} label={tracker.string.Scrum} kind={'primary'} on:click={showCreateDialog} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<List
|
||||||
|
_class={tracker.class.Scrum}
|
||||||
|
query={{ space: currentSpace }}
|
||||||
|
space={currentSpace}
|
||||||
|
config={[
|
||||||
|
{ key: '', presenter: Icon, props: { icon: tracker.icon.Scrum, size: 'small' } },
|
||||||
|
{ key: '', presenter: ScrumPresenter, props: { kind: 'list' } },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: contact.component.MembersPresenter,
|
||||||
|
props: {
|
||||||
|
kind: 'link',
|
||||||
|
intlTitle: tracker.string.ScrumMembersTitle,
|
||||||
|
intlSearchPh: tracker.string.ScrumMembersSearchPlaceholder,
|
||||||
|
retrieveMembers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ key: '', presenter: ScrumDatePresenter },
|
||||||
|
{ key: '', presenter: RecordScrumPresenter, props: { activeScrumRecord } },
|
||||||
|
{
|
||||||
|
key: 'modifiedOn',
|
||||||
|
presenter: tracker.component.ModificationDatePresenter
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
viewOptions={{ orderBy: ['beginTime', SortingOrder.Ascending], groupBy: [] }}
|
||||||
|
disableHeader
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.header {
|
||||||
|
padding: 0.5rem 0.75rem 0.5rem 2.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -77,6 +77,8 @@
|
|||||||
|
|
||||||
mode = newMode
|
mode = newMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retrieveMembers = (s: Sprint) => s.members
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="fs-title flex-between header">
|
<div class="fs-title flex-between header">
|
||||||
@ -171,7 +173,16 @@
|
|||||||
size: 'x-small'
|
size: 'x-small'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ key: '', presenter: tracker.component.SprintMembersPresenter, props: { kind: 'link' } },
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: contact.component.MembersPresenter,
|
||||||
|
props: {
|
||||||
|
kind: 'link',
|
||||||
|
intlTitle: tracker.string.SprintMembersTitle,
|
||||||
|
intlSearchPh: tracker.string.SprintMembersSearchPlaceholder,
|
||||||
|
retrieveMembers
|
||||||
|
}
|
||||||
|
},
|
||||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } },
|
{ key: '', presenter: SprintDatePresenter, props: { field: 'startDate' } },
|
||||||
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
|
{ key: '', presenter: SprintDatePresenter, props: { field: 'targetDate' } },
|
||||||
{ key: '', presenter: tracker.component.SprintStatusPresenter }
|
{ key: '', presenter: tracker.component.SprintStatusPresenter }
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
<!--
|
|
||||||
// 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 { Sprint } from '@hcengineering/tracker'
|
|
||||||
import { Button, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
|
||||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
|
||||||
import contact, { Employee } from '@hcengineering/contact'
|
|
||||||
import { getClient, UsersPopup } from '@hcengineering/presentation'
|
|
||||||
import { translate } from '@hcengineering/platform'
|
|
||||||
import tracker from '../../plugin'
|
|
||||||
|
|
||||||
export let value: Sprint
|
|
||||||
export let kind: ButtonKind = 'no-border'
|
|
||||||
export let size: ButtonSize = 'small'
|
|
||||||
export let justify: 'left' | 'center' = 'center'
|
|
||||||
export let width: string | undefined = 'min-content'
|
|
||||||
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
let buttonTitle = ''
|
|
||||||
|
|
||||||
$: translate(tracker.string.SprintMembersTitle, {}).then((res) => {
|
|
||||||
buttonTitle = res
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSprinttMembersChanged = async (result: Ref<Employee>[] | undefined) => {
|
|
||||||
if (result === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const memberToPull = value.members.filter((x) => !result.includes(x))[0]
|
|
||||||
const memberToPush = result.filter((x) => !value.members.includes(x))[0]
|
|
||||||
|
|
||||||
if (memberToPull) {
|
|
||||||
await client.update(value, { $pull: { members: memberToPull } })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memberToPush) {
|
|
||||||
await client.update(value, { $push: { members: memberToPush } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSprintMembersEditorOpened = async (event: MouseEvent) => {
|
|
||||||
showPopup(
|
|
||||||
UsersPopup,
|
|
||||||
{
|
|
||||||
_class: contact.class.Employee,
|
|
||||||
selectedUsers: value.members,
|
|
||||||
allowDeselect: true,
|
|
||||||
multiSelect: true,
|
|
||||||
docQuery: {
|
|
||||||
active: true
|
|
||||||
},
|
|
||||||
placeholder: tracker.string.SprintMembersSearchPlaceholder
|
|
||||||
},
|
|
||||||
eventToHTMLElement(event),
|
|
||||||
undefined,
|
|
||||||
handleSprinttMembersChanged
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
{kind}
|
|
||||||
{size}
|
|
||||||
{width}
|
|
||||||
{justify}
|
|
||||||
title={buttonTitle}
|
|
||||||
icon={tracker.icon.ProjectMembers}
|
|
||||||
on:click={handleSprintMembersEditorOpened}
|
|
||||||
/>
|
|
@ -15,11 +15,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { WithLookup } from '@hcengineering/core'
|
||||||
import { Sprint } from '@hcengineering/tracker'
|
import { Sprint } from '@hcengineering/tracker'
|
||||||
import { getCurrentLocation, navigate } from '@hcengineering/ui'
|
import { getCurrentLocation, Icon, navigate, tooltip } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<Sprint>
|
export let value: WithLookup<Sprint>
|
||||||
|
export let withIcon = false
|
||||||
|
export let onClick: () => void | undefined
|
||||||
|
|
||||||
function navigateToSprint () {
|
function navigateToSprint () {
|
||||||
|
if (onClick) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
|
||||||
const loc = getCurrentLocation()
|
const loc = getCurrentLocation()
|
||||||
|
loc.path[4] = 'sprints'
|
||||||
loc.path[5] = value._id
|
loc.path[5] = value._id
|
||||||
loc.path.length = 6
|
loc.path.length = 6
|
||||||
navigate(loc)
|
navigate(loc)
|
||||||
@ -28,20 +37,13 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-presenter flex-grow" on:click={navigateToSprint}>
|
<div class="flex" on:click={navigateToSprint}>
|
||||||
<span title={value.label} class="projectLabel flex-grow">{value.label}</span>
|
{#if withIcon}
|
||||||
|
<div class="mr-2" use:tooltip={{ label: tracker.string.Sprint }}>
|
||||||
|
<Icon icon={tracker.icon.Sprint} size={'small'} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<span title={value.label} class="cursor-pointer fs-bold caption-color overflow-label clear-mins">{value.label}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.projectLabel {
|
|
||||||
display: block;
|
|
||||||
min-width: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: left;
|
|
||||||
color: var(--theme-caption-color);
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { WithLookup } from '@hcengineering/core'
|
||||||
import type { IssueTemplate } from '@hcengineering/tracker'
|
import type { IssueTemplate } from '@hcengineering/tracker'
|
||||||
import { Icon, showPanel } from '@hcengineering/ui'
|
import { Icon, showPanel, tooltip } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let value: WithLookup<IssueTemplate>
|
export let value: WithLookup<IssueTemplate>
|
||||||
// export let inline: boolean = false
|
// export let inline: boolean = false
|
||||||
export let disableClick = false
|
export let disableClick = false
|
||||||
|
export let noUnderline = false
|
||||||
|
|
||||||
function handleIssueEditorOpened () {
|
function handleIssueEditorOpened () {
|
||||||
if (disableClick) {
|
if (disableClick) {
|
||||||
@ -34,9 +35,11 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span class="issuePresenterRoot flex" class:noPointer={disableClick} on:click={handleIssueEditorOpened}>
|
<span class="issuePresenterRoot" class:noPointer={disableClick} class:noUnderline on:click={handleIssueEditorOpened}>
|
||||||
<Icon icon={tracker.icon.Issues} size={'small'} />
|
<div class="mr-2" use:tooltip={{ label: tracker.string.IssueTemplate }}>
|
||||||
<span class="ml-2">
|
<Icon icon={tracker.icon.Issues} size={'small'} />
|
||||||
|
</div>
|
||||||
|
<span title={value?.title}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -44,6 +47,8 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.issuePresenterRoot {
|
.issuePresenterRoot {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -58,9 +63,18 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&.noUnderline {
|
||||||
|
font-weight: 500;
|
||||||
color: var(--caption-color);
|
color: var(--caption-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.noUnderline) {
|
||||||
|
&:hover {
|
||||||
|
color: var(--caption-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Class, Client, DocumentQuery, Ref, RelatedDocument, toIdMap, TxOperations } from '@hcengineering/core'
|
import {
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
DocumentQuery,
|
||||||
|
getCurrentAccount,
|
||||||
|
Ref,
|
||||||
|
RelatedDocument,
|
||||||
|
toIdMap,
|
||||||
|
TxOperations
|
||||||
|
} from '@hcengineering/core'
|
||||||
import { Resources, translate } from '@hcengineering/platform'
|
import { Resources, translate } from '@hcengineering/platform'
|
||||||
import { getClient, MessageBox, ObjectSearchResult } from '@hcengineering/presentation'
|
import { getClient, MessageBox, ObjectSearchResult } from '@hcengineering/presentation'
|
||||||
import { Issue, Sprint, Team } from '@hcengineering/tracker'
|
import { Issue, Scrum, ScrumRecord, Sprint, Team } from '@hcengineering/tracker'
|
||||||
import { showPopup } from '@hcengineering/ui'
|
import { showPopup } from '@hcengineering/ui'
|
||||||
import CreateIssue from './components/CreateIssue.svelte'
|
import CreateIssue from './components/CreateIssue.svelte'
|
||||||
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
import CreateIssueTemplate from './components/templates/CreateIssueTemplate.svelte'
|
||||||
@ -46,7 +55,6 @@ import EditProject from './components/projects/EditProject.svelte'
|
|||||||
import IconPresenter from './components/projects/IconPresenter.svelte'
|
import IconPresenter from './components/projects/IconPresenter.svelte'
|
||||||
import LeadPresenter from './components/projects/LeadPresenter.svelte'
|
import LeadPresenter from './components/projects/LeadPresenter.svelte'
|
||||||
import ProjectEditor from './components/projects/ProjectEditor.svelte'
|
import ProjectEditor from './components/projects/ProjectEditor.svelte'
|
||||||
import ProjectMembersPresenter from './components/projects/ProjectMembersPresenter.svelte'
|
|
||||||
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
|
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
|
||||||
import Projects from './components/projects/Projects.svelte'
|
import Projects from './components/projects/Projects.svelte'
|
||||||
import ProjectStatusEditor from './components/projects/ProjectStatusEditor.svelte'
|
import ProjectStatusEditor from './components/projects/ProjectStatusEditor.svelte'
|
||||||
@ -76,10 +84,12 @@ import SprintEditor from './components/sprints/SprintEditor.svelte'
|
|||||||
import SprintPresenter from './components/sprints/SprintPresenter.svelte'
|
import SprintPresenter from './components/sprints/SprintPresenter.svelte'
|
||||||
import Sprints from './components/sprints/Sprints.svelte'
|
import Sprints from './components/sprints/Sprints.svelte'
|
||||||
import SprintSelector from './components/sprints/SprintSelector.svelte'
|
import SprintSelector from './components/sprints/SprintSelector.svelte'
|
||||||
import SprintMembersPresenter from './components/sprints/SprintMembersPresenter.svelte'
|
|
||||||
import SprintStatusPresenter from './components/sprints/SprintStatusPresenter.svelte'
|
import SprintStatusPresenter from './components/sprints/SprintStatusPresenter.svelte'
|
||||||
import SprintTitlePresenter from './components/sprints/SprintTitlePresenter.svelte'
|
import SprintTitlePresenter from './components/sprints/SprintTitlePresenter.svelte'
|
||||||
|
|
||||||
|
import Scrums from './components/scrums/Scrums.svelte'
|
||||||
|
import ScrumRecordPanel from './components/scrums/ScrumRecordPanel.svelte'
|
||||||
|
|
||||||
import SubIssuesSelector from './components/issues/edit/SubIssuesSelector.svelte'
|
import SubIssuesSelector from './components/issues/edit/SubIssuesSelector.svelte'
|
||||||
import EstimationEditor from './components/issues/timereport/EstimationEditor.svelte'
|
import EstimationEditor from './components/issues/timereport/EstimationEditor.svelte'
|
||||||
import ReportedTimeEditor from './components/issues/timereport/ReportedTimeEditor.svelte'
|
import ReportedTimeEditor from './components/issues/timereport/ReportedTimeEditor.svelte'
|
||||||
@ -104,6 +114,7 @@ import TeamPresenter from './components/teams/TeamPresenter.svelte'
|
|||||||
import IssueStatistics from './components/sprints/IssueStatistics.svelte'
|
import IssueStatistics from './components/sprints/IssueStatistics.svelte'
|
||||||
import StatusRefPresenter from './components/issues/StatusRefPresenter.svelte'
|
import StatusRefPresenter from './components/issues/StatusRefPresenter.svelte'
|
||||||
import SprintRefPresenter from './components/sprints/SprintRefPresenter.svelte'
|
import SprintRefPresenter from './components/sprints/SprintRefPresenter.svelte'
|
||||||
|
import { EmployeeAccount } from '@hcengineering/contact'
|
||||||
|
|
||||||
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
export { default as SubIssueList } from './components/issues/edit/SubIssueList.svelte'
|
||||||
|
|
||||||
@ -216,6 +227,79 @@ async function deleteSprint (sprint: Sprint): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function startRecordingScrum (
|
||||||
|
client: TxOperations,
|
||||||
|
newRecordingScrum: Scrum,
|
||||||
|
previousScrumRecord?: ScrumRecord
|
||||||
|
): Promise<void> {
|
||||||
|
const newRecordLabel = `${newRecordingScrum.title}-${newRecordingScrum.scrumRecords ?? 0}`
|
||||||
|
const startRecord = async (): Promise<void> => {
|
||||||
|
await client.addCollection(
|
||||||
|
tracker.class.ScrumRecord,
|
||||||
|
newRecordingScrum.space,
|
||||||
|
newRecordingScrum._id,
|
||||||
|
tracker.class.Scrum,
|
||||||
|
'scrumRecords',
|
||||||
|
{
|
||||||
|
label: newRecordLabel,
|
||||||
|
scrumRecorder: getCurrentAccount()._id as Ref<EmployeeAccount>,
|
||||||
|
startTs: Date.now(),
|
||||||
|
comments: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousScrumRecord !== undefined) {
|
||||||
|
showPopup(
|
||||||
|
MessageBox,
|
||||||
|
{
|
||||||
|
label: tracker.string.ChangeScrumRecord,
|
||||||
|
message: tracker.string.ChangeScrumRecordConfirm,
|
||||||
|
params: { previousRecord: previousScrumRecord.label, newRecord: newRecordLabel }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
(result?: boolean) => {
|
||||||
|
if (result === true) {
|
||||||
|
void client
|
||||||
|
.updateCollection(
|
||||||
|
tracker.class.ScrumRecord,
|
||||||
|
previousScrumRecord.space,
|
||||||
|
previousScrumRecord._id,
|
||||||
|
previousScrumRecord.attachedTo,
|
||||||
|
tracker.class.Scrum,
|
||||||
|
'scrumRecords',
|
||||||
|
{ endTs: Date.now() }
|
||||||
|
)
|
||||||
|
.then(async () => await startRecord())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await startRecord()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleRecordingScrum (
|
||||||
|
client: TxOperations,
|
||||||
|
currentScrum: Scrum,
|
||||||
|
activeScrumRecord?: ScrumRecord
|
||||||
|
): Promise<void> {
|
||||||
|
// Stop recording scrum if active record attached to current scrum
|
||||||
|
if (activeScrumRecord?.attachedTo === currentScrum._id) {
|
||||||
|
await client.updateCollection(
|
||||||
|
tracker.class.ScrumRecord,
|
||||||
|
activeScrumRecord.space,
|
||||||
|
activeScrumRecord._id,
|
||||||
|
activeScrumRecord.attachedTo,
|
||||||
|
tracker.class.Scrum,
|
||||||
|
'scrumRecords',
|
||||||
|
{ endTs: Date.now() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await startRecordingScrum(client, currentScrum, activeScrumRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
component: {
|
component: {
|
||||||
NopeComponent,
|
NopeComponent,
|
||||||
@ -245,7 +329,6 @@ export default async (): Promise<Resources> => ({
|
|||||||
IconPresenter,
|
IconPresenter,
|
||||||
LeadPresenter,
|
LeadPresenter,
|
||||||
TargetDatePresenter,
|
TargetDatePresenter,
|
||||||
ProjectMembersPresenter,
|
|
||||||
ProjectStatusPresenter,
|
ProjectStatusPresenter,
|
||||||
ProjectStatusEditor,
|
ProjectStatusEditor,
|
||||||
SetDueDateActionPopup,
|
SetDueDateActionPopup,
|
||||||
@ -261,7 +344,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
CreateIssueTemplate,
|
CreateIssueTemplate,
|
||||||
Sprints,
|
Sprints,
|
||||||
SprintPresenter,
|
SprintPresenter,
|
||||||
SprintMembersPresenter,
|
Scrums,
|
||||||
|
ScrumRecordPanel,
|
||||||
SprintStatusPresenter,
|
SprintStatusPresenter,
|
||||||
SprintTitlePresenter,
|
SprintTitlePresenter,
|
||||||
SprintSelector,
|
SprintSelector,
|
||||||
|
@ -244,6 +244,26 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
MoveAndDeleteSprint: '' as IntlString,
|
MoveAndDeleteSprint: '' as IntlString,
|
||||||
MoveAndDeleteSprintConfirm: '' as IntlString,
|
MoveAndDeleteSprintConfirm: '' as IntlString,
|
||||||
|
|
||||||
|
Scrum: '' as IntlString,
|
||||||
|
Scrums: '' as IntlString,
|
||||||
|
ScrumMembersTitle: '' as IntlString,
|
||||||
|
ScrumMembersSearchPlaceholder: '' as IntlString,
|
||||||
|
ScrumBeginTime: '' as IntlString,
|
||||||
|
ScrumEndTime: '' as IntlString,
|
||||||
|
NewScrum: '' as IntlString,
|
||||||
|
CreateScrum: '' as IntlString,
|
||||||
|
ScrumTitlePlaceholder: '' as IntlString,
|
||||||
|
ScrumDescriptionPlaceholder: '' as IntlString,
|
||||||
|
ScrumRecords: '' as IntlString,
|
||||||
|
ScrumRecord: '' as IntlString,
|
||||||
|
StartRecord: '' as IntlString,
|
||||||
|
StopRecord: '' as IntlString,
|
||||||
|
ChangeScrumRecord: '' as IntlString,
|
||||||
|
ChangeScrumRecordConfirm: '' as IntlString,
|
||||||
|
ScrumRecorder: '' as IntlString,
|
||||||
|
ScrumRecordTimeReports: '' as IntlString,
|
||||||
|
ScrumRecordObjects: '' as IntlString,
|
||||||
|
|
||||||
Estimation: '' as IntlString,
|
Estimation: '' as IntlString,
|
||||||
ReportedTime: '' as IntlString,
|
ReportedTime: '' as IntlString,
|
||||||
TimeSpendReport: '' as IntlString,
|
TimeSpendReport: '' as IntlString,
|
||||||
@ -311,7 +331,6 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
IconPresenter: '' as AnyComponent,
|
IconPresenter: '' as AnyComponent,
|
||||||
LeadPresenter: '' as AnyComponent,
|
LeadPresenter: '' as AnyComponent,
|
||||||
TargetDatePresenter: '' as AnyComponent,
|
TargetDatePresenter: '' as AnyComponent,
|
||||||
ProjectMembersPresenter: '' as AnyComponent,
|
|
||||||
ProjectStatusPresenter: '' as AnyComponent,
|
ProjectStatusPresenter: '' as AnyComponent,
|
||||||
ProjectStatusEditor: '' as AnyComponent,
|
ProjectStatusEditor: '' as AnyComponent,
|
||||||
SetDueDateActionPopup: '' as AnyComponent,
|
SetDueDateActionPopup: '' as AnyComponent,
|
||||||
@ -328,16 +347,19 @@ export default mergeIds(trackerId, tracker, {
|
|||||||
SprintPresenter: '' as AnyComponent,
|
SprintPresenter: '' as AnyComponent,
|
||||||
SprintStatusPresenter: '' as AnyComponent,
|
SprintStatusPresenter: '' as AnyComponent,
|
||||||
SprintTitlePresenter: '' as AnyComponent,
|
SprintTitlePresenter: '' as AnyComponent,
|
||||||
SprintMembersPresenter: '' as AnyComponent,
|
|
||||||
ReportedTimeEditor: '' as AnyComponent,
|
ReportedTimeEditor: '' as AnyComponent,
|
||||||
TimeSpendReport: '' as AnyComponent,
|
TimeSpendReport: '' as AnyComponent,
|
||||||
EstimationEditor: '' as AnyComponent,
|
EstimationEditor: '' as AnyComponent,
|
||||||
TemplateEstimationEditor: '' as AnyComponent,
|
TemplateEstimationEditor: '' as AnyComponent,
|
||||||
|
|
||||||
|
Scrums: '' as AnyComponent,
|
||||||
|
ScrumRecordPanel: '' as AnyComponent,
|
||||||
|
|
||||||
ProjectSelector: '' as AnyComponent,
|
ProjectSelector: '' as AnyComponent,
|
||||||
|
|
||||||
IssueTemplates: '' as AnyComponent,
|
IssueTemplates: '' as AnyComponent,
|
||||||
IssueTemplatePresenter: '' as AnyComponent
|
IssueTemplatePresenter: '' as AnyComponent,
|
||||||
|
SubIssuesSelector: '' as AnyComponent
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
CreateIssueDraft: '' as Metadata<IssueDraft>
|
CreateIssueDraft: '' as Metadata<IssueDraft>
|
||||||
|
@ -258,6 +258,8 @@ export type ProjectsViewMode = 'all' | 'backlog' | 'active' | 'closed'
|
|||||||
|
|
||||||
export type SprintViewMode = 'all' | 'planned' | 'active' | 'closed'
|
export type SprintViewMode = 'all' | 'planned' | 'active' | 'closed'
|
||||||
|
|
||||||
|
export type ScrumRecordViewMode = 'timeReports' | 'objects'
|
||||||
|
|
||||||
export const getIncludedProjectStatuses = (mode: ProjectsViewMode): ProjectStatus[] => {
|
export const getIncludedProjectStatuses = (mode: ProjectsViewMode): ProjectStatus[] => {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'all': {
|
case 'all': {
|
||||||
@ -312,6 +314,11 @@ export const sprintTitleMap: Record<SprintViewMode, IntlString> = Object.freeze(
|
|||||||
closed: tracker.string.ClosedSprints
|
closed: tracker.string.ClosedSprints
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const scrumRecordTitleMap: Record<ScrumRecordViewMode, IntlString> = Object.freeze({
|
||||||
|
timeReports: tracker.string.ScrumRecordTimeReports,
|
||||||
|
objects: tracker.string.ScrumRecordObjects
|
||||||
|
})
|
||||||
|
|
||||||
const listIssueStatusOrder = [
|
const listIssueStatusOrder = [
|
||||||
tracker.issueStatusCategory.Started,
|
tracker.issueStatusCategory.Started,
|
||||||
tracker.issueStatusCategory.Unstarted,
|
tracker.issueStatusCategory.Unstarted,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Employee } from '@hcengineering/contact'
|
import { Employee, EmployeeAccount } from '@hcengineering/contact'
|
||||||
import type { AttachedDoc, Class, Doc, Markup, Ref, RelatedDocument, Space, Timestamp, Type } from '@hcengineering/core'
|
import type { AttachedDoc, Class, Doc, Markup, Ref, RelatedDocument, Space, Timestamp, Type } from '@hcengineering/core'
|
||||||
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
@ -357,6 +357,37 @@ export interface Project extends Doc {
|
|||||||
documents: number
|
documents: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ScrumRecord extends AttachedDoc {
|
||||||
|
label: string
|
||||||
|
startTs: Timestamp
|
||||||
|
endTs?: Timestamp
|
||||||
|
scrumRecorder: Ref<EmployeeAccount>
|
||||||
|
|
||||||
|
comments: number
|
||||||
|
attachments?: number
|
||||||
|
|
||||||
|
space: Ref<Team>
|
||||||
|
attachedTo: Ref<Scrum>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface Scrum extends Doc {
|
||||||
|
title: string
|
||||||
|
description?: Markup
|
||||||
|
beginTime: Timestamp
|
||||||
|
endTime: Timestamp
|
||||||
|
members: Ref<Employee>[]
|
||||||
|
space: Ref<Team>
|
||||||
|
|
||||||
|
scrumRecords?: number
|
||||||
|
attachments?: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -388,6 +419,8 @@ export default plugin(trackerId, {
|
|||||||
TypeIssuePriority: '' as Ref<Class<Type<IssuePriority>>>,
|
TypeIssuePriority: '' as Ref<Class<Type<IssuePriority>>>,
|
||||||
TypeProjectStatus: '' as Ref<Class<Type<ProjectStatus>>>,
|
TypeProjectStatus: '' as Ref<Class<Type<ProjectStatus>>>,
|
||||||
Sprint: '' as Ref<Class<Sprint>>,
|
Sprint: '' as Ref<Class<Sprint>>,
|
||||||
|
Scrum: '' as Ref<Class<Scrum>>,
|
||||||
|
ScrumRecord: '' as Ref<Class<ScrumRecord>>,
|
||||||
TypeSprintStatus: '' as Ref<Class<Type<SprintStatus>>>,
|
TypeSprintStatus: '' as Ref<Class<Type<SprintStatus>>>,
|
||||||
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
TimeSpendReport: '' as Ref<Class<TimeSpendReport>>,
|
||||||
TypeReportedTime: '' as Ref<Class<Type<number>>>
|
TypeReportedTime: '' as Ref<Class<Type<number>>>
|
||||||
@ -432,6 +465,9 @@ export default plugin(trackerId, {
|
|||||||
DueDate: '' as Asset,
|
DueDate: '' as Asset,
|
||||||
Parent: '' as Asset,
|
Parent: '' as Asset,
|
||||||
Sprint: '' as Asset,
|
Sprint: '' as Asset,
|
||||||
|
Scrum: '' as Asset,
|
||||||
|
Start: '' as Asset,
|
||||||
|
Stop: '' as Asset,
|
||||||
|
|
||||||
CategoryBacklog: '' as Asset,
|
CategoryBacklog: '' as Asset,
|
||||||
CategoryUnstarted: '' as Asset,
|
CategoryUnstarted: '' as Asset,
|
||||||
|
@ -22,17 +22,14 @@
|
|||||||
// export let label: IntlString
|
// export let label: IntlString
|
||||||
export let onChange: (value: any) => void
|
export let onChange: (value: any) => void
|
||||||
export let kind: 'no-border' | 'link' = 'no-border'
|
export let kind: 'no-border' | 'link' = 'no-border'
|
||||||
|
|
||||||
$: withTime = type?.withTime ?? true
|
|
||||||
$: withShift = type?.withShift ?? true
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DateRangePresenter
|
<DateRangePresenter
|
||||||
{value}
|
{value}
|
||||||
{withTime}
|
mode={type?.mode}
|
||||||
|
noShift={!type?.withShift}
|
||||||
editable
|
editable
|
||||||
{kind}
|
{kind}
|
||||||
noShift={!withShift}
|
|
||||||
on:change={(res) => {
|
on:change={(res) => {
|
||||||
if (res.detail !== undefined) onChange(res.detail)
|
if (res.detail !== undefined) onChange(res.detail)
|
||||||
}}
|
}}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import ClassAttributeBar from './ClassAttributeBar.svelte'
|
import ClassAttributeBar from './ClassAttributeBar.svelte'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
export let mixins: Mixin<Doc>[]
|
export let mixins: Mixin<Doc>[] = []
|
||||||
export let ignoreKeys: string[]
|
export let ignoreKeys: string[]
|
||||||
export let allowedCollections: string[] = []
|
export let allowedCollections: string[] = []
|
||||||
|
|
||||||
|
@ -32,9 +32,10 @@
|
|||||||
export let loadingProps: LoadingProps | undefined = undefined
|
export let loadingProps: LoadingProps | undefined = undefined
|
||||||
export let createItemDialog: AnyComponent | undefined = undefined
|
export let createItemDialog: AnyComponent | undefined = undefined
|
||||||
export let createItemLabel: IntlString | undefined = undefined
|
export let createItemLabel: IntlString | undefined = undefined
|
||||||
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
export let viewOptionsConfig: ViewOptionModel[] | undefined = undefined
|
||||||
export let viewOptions: ViewOptions
|
export let viewOptions: ViewOptions
|
||||||
export let flatHeaders = false
|
export let flatHeaders = false
|
||||||
|
export let disableHeader = false
|
||||||
export let props: Record<string, any> = {}
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
export let documents: Doc[] | undefined = undefined
|
export let documents: Doc[] | undefined = undefined
|
||||||
@ -154,6 +155,7 @@
|
|||||||
on:uncheckAll={uncheckAll}
|
on:uncheckAll={uncheckAll}
|
||||||
on:row-focus
|
on:row-focus
|
||||||
{flatHeaders}
|
{flatHeaders}
|
||||||
|
{disableHeader}
|
||||||
{props}
|
{props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
export let createItemLabel: IntlString | undefined
|
export let createItemLabel: IntlString | undefined
|
||||||
export let viewOptions: ViewOptions
|
export let viewOptions: ViewOptions
|
||||||
export let flatHeaders = false
|
export let flatHeaders = false
|
||||||
|
export let disableHeader = false
|
||||||
export let props: Record<string, any> = {}
|
export let props: Record<string, any> = {}
|
||||||
export let level: number
|
export let level: number
|
||||||
export let initIndex = 0
|
export let initIndex = 0
|
||||||
@ -121,6 +122,7 @@
|
|||||||
on:uncheckAll
|
on:uncheckAll
|
||||||
on:row-focus
|
on:row-focus
|
||||||
{flatHeaders}
|
{flatHeaders}
|
||||||
|
{disableHeader}
|
||||||
{props}
|
{props}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
export let itemModels: AttributeModel[]
|
export let itemModels: AttributeModel[]
|
||||||
export let extraHeaders: AnyComponent[] | undefined
|
export let extraHeaders: AnyComponent[] | undefined
|
||||||
export let flatHeaders = false
|
export let flatHeaders = false
|
||||||
|
export let disableHeader = false
|
||||||
export let props: Record<string, any> = {}
|
export let props: Record<string, any> = {}
|
||||||
export let level: number
|
export let level: number
|
||||||
export let elementByIndex: Map<number, HTMLDivElement>
|
export let elementByIndex: Map<number, HTMLDivElement>
|
||||||
@ -74,7 +75,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initCollapsed (singleCat: boolean, lastLevel: boolean, category: any): void {
|
function initCollapsed (singleCat: boolean, lastLevel: boolean, category: any): void {
|
||||||
collapsed = !singleCat && items.length > (lastLevel ? autoFoldLimit : singleCategoryLimit)
|
collapsed = !disableHeader && !singleCat && items.length > (lastLevel ? autoFoldLimit : singleCategoryLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: initCollapsed(singleCat, lastLevel, category)
|
$: initCollapsed(singleCat, lastLevel, category)
|
||||||
@ -108,27 +109,29 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ListHeader
|
{#if !disableHeader}
|
||||||
{groupByKey}
|
<ListHeader
|
||||||
{category}
|
{groupByKey}
|
||||||
{space}
|
{category}
|
||||||
{level}
|
{space}
|
||||||
limited={limited.length}
|
{level}
|
||||||
{items}
|
limited={limited.length}
|
||||||
{headerComponent}
|
{items}
|
||||||
{createItemDialog}
|
{headerComponent}
|
||||||
{createItemLabel}
|
{createItemDialog}
|
||||||
{extraHeaders}
|
{createItemLabel}
|
||||||
{newObjectProps}
|
{extraHeaders}
|
||||||
flat={flatHeaders}
|
{newObjectProps}
|
||||||
{props}
|
flat={flatHeaders}
|
||||||
on:more={() => {
|
{props}
|
||||||
limit += 20
|
on:more={() => {
|
||||||
}}
|
limit += 20
|
||||||
on:collapse={() => {
|
}}
|
||||||
collapsed = !collapsed
|
on:collapse={() => {
|
||||||
}}
|
collapsed = !collapsed
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<ExpandCollapse isExpanded={!collapsed} duration={400}>
|
<ExpandCollapse isExpanded={!collapsed} duration={400}>
|
||||||
{#if !lastLevel}
|
{#if !lastLevel}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
|
Loading…
Reference in New Issue
Block a user