Tracker: Issue List - DueDate presenter (#1393)

This commit is contained in:
Artyom Grigorovich 2022-04-14 13:07:07 +07:00 committed by GitHub
parent 4ba579b517
commit e83ffd3804
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 416 additions and 129 deletions

View File

@ -18,7 +18,7 @@
import contact, { Contact, formatName } from '@anticrm/contact'
import type { Class, Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import type { TooltipAligment } from '@anticrm/ui'
import type { TooltipAlignment } from '@anticrm/ui'
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
@ -39,7 +39,7 @@
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const dispatch = createEventDispatcher()

View File

@ -15,14 +15,14 @@
<script lang="ts">
import type { IntlString, Asset } from '@anticrm/platform'
import type { AnySvelteComponent, TooltipAligment } from '../types'
import type { AnySvelteComponent, TooltipAlignment } from '../types'
import Icon from './Icon.svelte'
import Tooltip from './Tooltip.svelte'
export let label: IntlString = '' as IntlString
export let labelProps: any = undefined
export let direction: TooltipAligment | undefined = undefined
export let direction: TooltipAlignment | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: (ev: Event) => Promise<void> | void = async () => { }

View File

@ -20,7 +20,7 @@
import Label from './Label.svelte'
import Icon from './Icon.svelte'
import { showPopup, Button, Tooltip, DropdownPopup } from '..'
import type { AnySvelteComponent, ListItem, TooltipAligment } from '../types'
import type { AnySvelteComponent, ListItem, TooltipAlignment } from '../types'
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let label: IntlString
@ -32,7 +32,7 @@
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let container: HTMLElement
let opened: boolean = false

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { IntlString, Asset } from '@anticrm/platform'
import DropdownLabelsPopup from './DropdownLabelsPopup.svelte'
import type { AnySvelteComponent, DropdownTextItem, TooltipAligment } from '../types'
import type { AnySvelteComponent, DropdownTextItem, TooltipAlignment } from '../types'
import { showPopup, Tooltip, Button, Label } from '..'
import { createEventDispatcher } from 'svelte'
import ui from '../plugin'
@ -31,7 +31,7 @@
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
let container: HTMLElement
let opened: boolean = false

View File

@ -14,11 +14,11 @@
-->
<script lang="ts">
import type { IntlString } from '@anticrm/platform'
import type { TooltipAligment, AnySvelteComponent, AnyComponent } from '..'
import type { TooltipAlignment, AnySvelteComponent, AnyComponent } from '..'
import { tooltipstore as tooltip, showTooltip } from '..'
export let label: IntlString | undefined = undefined
export let direction: TooltipAligment | undefined = undefined
export let direction: TooltipAlignment | undefined = undefined
export let component: AnySvelteComponent | AnyComponent | undefined = undefined
export let props: any | undefined = undefined
export let anchor: HTMLElement | undefined = undefined

View File

@ -15,12 +15,12 @@
<script lang="ts">
import { afterUpdate, onDestroy } from 'svelte'
import { tooltipstore as tooltip, closeTooltip, Component } from '..'
import type { TooltipAligment } from '..'
import type { TooltipAlignment } from '..'
import Label from './Label.svelte'
let tooltipHTML: HTMLElement
let nubHTML: HTMLElement
let dir: TooltipAligment
let dir: TooltipAlignment
let rect: DOMRect
let rectAnchor: DOMRect
let tooltipSW: boolean // tooltipSW = true - Label; false - Component

View File

@ -25,10 +25,11 @@
export let withTime: boolean = false
export let mondayStart: boolean = true
export let editable: boolean = false
export let icon: 'normal' | 'warning' | 'overdue' = 'normal'
export let icon: 'normal' | 'warning' | 'critical' | 'overdue' = 'normal'
export let labelOver: IntlString | undefined = undefined // label instead of date
export let labelNull: IntlString = ui.string.NoDate
export let showIcon = true
export let shouldShowLabel: boolean = true
const dispatch = createEventDispatcher()
@ -54,27 +55,35 @@
<button
class="datetime-button"
class:editable
class:dateTimeButtonNoLabel={!shouldShowLabel}
on:click={() => {
if (editable && !opened) {
opened = true
showPopup(DatePopup,
{ currentDate, mondayStart, withTime },
undefined,
() => { opened = false },
(result) => { if (result !== undefined) onChange(result) })
showPopup(
DatePopup,
{ currentDate, mondayStart, withTime },
undefined,
() => {
opened = false
},
(result) => {
if (result !== undefined) onChange(result)
}
)
}
}}
>
{#if showIcon}
<div class="btn-icon {icon}">
<Icon icon={icon === 'overdue' ? DPCalendarOver : DPCalendar} size={'full'}/>
<div class="btn-icon {icon}" class:buttonIconNoLabel={!shouldShowLabel}>
<Icon icon={icon === 'overdue' ? DPCalendarOver : DPCalendar} size={'full'} />
</div>
{/if}
{#if value !== null && value !== undefined}
{#if labelOver !== undefined}
{#if shouldShowLabel && labelOver !== undefined}
<Label label={labelOver} />
{:else}
{new Date(value).getDate()} {getMonthName(new Date(value), 'short')}
{:else if shouldShowLabel}
{new Date(value).getDate()}
{getMonthName(new Date(value), 'short')}
{#if new Date(value).getFullYear() !== today.getFullYear()}
{new Date(value).getFullYear()}
{/if}
@ -85,7 +94,7 @@
{new Date(value).getMinutes().toString().padStart(2, '0')}
{/if}
{/if}
{:else}
{:else if shouldShowLabel}
<Label label={labelNull} />
{/if}
</button>
@ -96,7 +105,7 @@
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0 .5rem;
padding: 0 0.5rem;
font-weight: 400;
min-width: 1.5rem;
width: auto;
@ -106,22 +115,38 @@
color: var(--accent-color);
background-color: var(--button-bg-color);
border: 1px solid transparent;
border-radius: .25rem;
border-radius: 0.25rem;
box-shadow: var(--button-shadow);
transition-property: border, background-color, color, box-shadow;
transition-duration: .15s;
transition-duration: 0.15s;
cursor: default;
&.dateTimeButtonNoLabel {
padding: 0;
}
.btn-icon {
margin-right: .375rem;
width: .875rem;
height: .875rem;
transition: color .15s;
margin-right: 0.375rem;
width: 0.875rem;
height: 0.875rem;
transition: color 0.15s;
pointer-events: none;
&.normal { color: var(--content-color); }
&.warning { color: var(--warning-color); }
&.overdue { color: var(--error-color); }
&.buttonIconNoLabel {
margin-right: 0;
}
&.normal {
color: var(--content-color);
}
&.warning {
color: var(--warning-color);
}
&.critical {
color: var(--error-color);
}
&.overdue {
color: var(--error-color);
}
}
&:hover {
@ -134,15 +159,28 @@
&:hover {
background-color: var(--button-bg-hover);
.btn-icon {
&.normal { color: var(--caption-color); }
&.warning { color: var(--warning-color); }
&.overdue { color: var(--error-color); }
&.normal {
color: var(--caption-color);
}
&.warning {
color: var(--warning-color);
}
&.critical {
color: var(--error-color);
}
&.overdue {
color: var(--error-color);
}
}
.time-divider {
background-color: var(--button-border-hover);
}
.time-divider { background-color: var(--button-border-hover); }
}
&:focus-within {
border-color: var(--primary-edit-border-color);
&:hover { background-color: transparent; }
&:hover {
background-color: transparent;
}
}
}
&:disabled {
@ -151,17 +189,21 @@
&:hover {
color: var(--content-color);
.btn-icon { color: var(--content-color); }
.btn-icon {
color: var(--content-color);
}
}
}
.time-divider {
flex-shrink: 0;
margin: 0 .25rem;
margin: 0 0.25rem;
width: 1px;
min-width: 1px;
height: .75rem;
height: 0.75rem;
background-color: var(--button-border-color);
}
.separator { margin: 0 .1rem; }
.separator {
margin: 0 0.1rem;
}
}
</style>

View File

@ -78,6 +78,7 @@ export function getMonthName (date: Date, option: 'narrow' | 'short' | 'long' |
return new Intl.DateTimeFormat(locale, { month: option }).format(date)
} catch (err) {
console.error(err)
return ''
}
}
@ -100,3 +101,10 @@ export function addZero (value: number): string {
}
return `${value}`
}
export const getDaysDifference = (from: Date, to: Date): number => {
const firstDateMs = from.setHours(0, 0, 0, 0)
const secondDateMs = to.setHours(0, 0, 0, 0)
return Math.round(Math.abs(secondDateMs - firstDateMs) / MILLISECONDS_IN_DAY)
}

View File

@ -20,7 +20,7 @@ import { readable } from 'svelte/store'
import Root from './components/internal/Root.svelte'
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAligment, AnySvelteComponentWithProps, Location, PopupAlignment } from './types'
export type { AnyComponent, AnySvelteComponent, Action, LabelAndProps, TooltipAlignment, AnySvelteComponentWithProps, Location, PopupAlignment } from './types'
// export { applicationShortcutKey } from './utils'
export { getCurrentLocation, locationToUrl, navigate, location } from './location'
@ -102,6 +102,8 @@ export { default as IconCheck } from './components/icons/Check.svelte'
export { default as IconArrowLeft } from './components/icons/ArrowLeft.svelte'
export { default as IconNavPrev } from './components/icons/NavPrev.svelte'
export { default as IconNavNext } from './components/icons/NavNext.svelte'
export { default as IconDPCalendar } from './components/calendar/icons/DPCalendar.svelte'
export { default as IconDPCalendarOver } from './components/calendar/icons/DPCalendarOver.svelte'
export { default as PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte'
@ -110,6 +112,7 @@ export * from './utils'
export * from './popups'
export * from './tooltips'
export * from './panelup'
export * from './components/calendar/internal/DateUtils'
export function createApp (target: HTMLElement): SvelteComponent {
return new Root({ target })

View File

@ -1,4 +1,4 @@
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAligment } from './types'
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAlignment } from './types'
import { IntlString } from '@anticrm/platform'
import { writable } from 'svelte/store'
@ -15,7 +15,7 @@ export const tooltipstore = writable<LabelAndProps>({
export function showTooltip (
label: IntlString | undefined,
element: HTMLElement,
direction?: TooltipAligment,
direction?: TooltipAlignment,
component?: AnySvelteComponent | AnyComponent,
props?: any,
anchor?: HTMLElement,

View File

@ -65,12 +65,12 @@ export type TabModel = Tab[]
export type PopupAlignment = HTMLElement | EventTarget | null | 'right' | 'top' | 'account' | 'full' | 'content' | 'middle'
export type TooltipAligment = 'top' | 'bottom' | 'left' | 'right'
export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
export interface LabelAndProps {
label: IntlString | undefined
element: HTMLElement | undefined
direction?: TooltipAligment
direction?: TooltipAlignment
component?: AnySvelteComponent | AnyComponent
props?: any
anchor: HTMLElement | undefined

View File

@ -19,7 +19,7 @@
import { IntlString } from '@anticrm/platform'
import { createQuery } from '@anticrm/presentation'
import { Dropdown } from '@anticrm/ui'
import type { TooltipAligment } from '@anticrm/ui'
import type { TooltipAlignment } from '@anticrm/ui'
import { ListItem } from '@anticrm/ui/src/types'
import { createEventDispatcher } from 'svelte'
import contact from '../plugin'
@ -32,7 +32,7 @@
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let labelDirection: TooltipAligment | undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
const query = createQuery()
const dispatch = createEventDispatcher()

View File

@ -27,6 +27,7 @@
"Canceled": "Canceled",
"SetPriority": "Set priority\u2026",
"SetStatus": "Set status\u2026",
"SelectIssue": "Select issue",
"Priority": "Priority",
"NoPriority": "No priority",
"Urgent": "Urgent",
@ -51,7 +52,7 @@
"Project": "Project",
"Space": "",
"DueDate": "Set due date\u2026",
"ModificationDate": "Last Modified: {value}",
"ModificationDate": "Updated {value}",
"Team": "",
"Issue": "",
"Document": "",
@ -60,7 +61,11 @@
"Rank": "",
"IssueTitlePlaceholder": "Issue title",
"IssueDescriptionPlaceholder": "Add description",
"AddIssueTooltip": "Add issue..."
"AddIssueTooltip": "Add issue...",
"DueDatePopupTitle": "Due on {value}",
"DueDatePopupOverdueTitle": "Was due on {value}",
"DueDatePopupDescription": "{value, plural, =0 {Today} =1 {Tomorrow} other {# days remaining}}",
"DueDatePopupOverdueDescription": "{value, plural, =1 {1 day overdue} other {# days overdue}}"
},
"status": {}
}

View File

@ -91,8 +91,6 @@
{ icon: tracker.icon.Parent, label: tracker.string.Parent }
]
let issueDate: number | null = null
const handlePriorityChanged = (newPriority: IssuePriority | undefined) => {
if (newPriority === undefined) {
return
@ -166,7 +164,7 @@
size="small"
kind="no-border"
/>
<DatePresenter value={issueDate} editable />
<DatePresenter bind:value={object.dueDate} editable />
<Button
icon={tracker.icon.MoreActions}
width="min-content"

View File

@ -52,7 +52,7 @@
/>
{:else if kind === 'icon'}
<div class="flex-presenter" on:click={handlePriorityEditorOpened}>
<div class="icon">
<div class="priorityIcon">
<Icon icon={issuePriorities[priority].icon} size={'small'} />
</div>
{#if shouldShowLabel}
@ -62,3 +62,15 @@
{/if}
</div>
{/if}
<style lang="scss">
.priorityIcon {
width: 1rem;
height: 1rem;
color: var(--theme-content-dark-color);
&:hover {
color: var(--theme-caption-color);
}
}
</style>

View File

@ -52,7 +52,7 @@
/>
{:else if kind === 'icon'}
<div class="flex-presenter" on:click={handleStatusEditorOpened}>
<div class="icon">
<div class="statusIcon">
<Icon icon={issueStatuses[status].icon} size={'small'} />
</div>
{#if shouldShowLabel}
@ -62,3 +62,10 @@
{/if}
</div>
{/if}
<style lang="scss">
.statusIcon {
width: 1rem;
height: 1rem;
}
</style>

View File

@ -49,11 +49,14 @@
<Scroller>
<IssuesList
_class={tracker.class.Issue}
config={[
leftItemsConfig={[
{ key: '', presenter: tracker.component.PriorityPresenter, props: { currentSpace } },
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
{ key: '', presenter: tracker.component.StatusPresenter, props: { currentSpace } },
{ key: '', presenter: tracker.component.TitlePresenter },
{ key: '', presenter: tracker.component.TitlePresenter }
]}
rightItemsConfig={[
{ key: '', presenter: tracker.component.DueDatePresenter, props: { currentSpace } },
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
{ key: '', presenter: tracker.component.AssigneePresenter, props: { currentSpace } }
]}

View File

@ -0,0 +1,85 @@
<!--
// 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 { Icon, Label, IconDPCalendarOver, IconDPCalendar } from '@anticrm/ui'
import tracker from '../../plugin'
export let formattedDate: string = ''
export let daysDifference: number = 0
export let isOverdue: boolean = false
export let iconModifier: 'warning' | 'critical' | 'overdue' | undefined = undefined
</script>
{#if formattedDate}
<div class="root">
<div
class="iconContainer"
class:mIconContainerWarning={iconModifier === 'warning'}
class:mIconContainerCritical={iconModifier === 'critical' || iconModifier === 'overdue'}
>
<Icon icon={isOverdue ? IconDPCalendarOver : IconDPCalendar} size={'small'} />
</div>
<div class="messageContainer">
<div class="title">
<Label
label={isOverdue ? tracker.string.DueDatePopupOverdueTitle : tracker.string.DueDatePopupTitle}
params={{ value: formattedDate }}
/>
</div>
<div class="description">
<Label
label={isOverdue ? tracker.string.DueDatePopupOverdueDescription : tracker.string.DueDatePopupDescription}
params={{ value: daysDifference }}
/>
</div>
</div>
</div>
{/if}
<style lang="scss">
.root {
display: flex;
width: 15rem;
}
.iconContainer {
color: var(--content-color);
margin-right: 1rem;
&.mIconContainerWarning {
color: var(--warning-color);
}
&.mIconContainerCritical {
color: var(--error-color);
}
}
.messageContainer {
display: flex;
flex-direction: column;
justify-content: center;
}
.title {
color: var(--theme-caption-color);
font-weight: 500;
}
.description {
margin-top: 0.25rem;
color: var(--theme-content-dark-color);
}
</style>

View File

@ -0,0 +1,89 @@
<!--
// 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, Timestamp } from '@anticrm/core'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { DatePresenter, Tooltip, getDaysDifference } from '@anticrm/ui'
import { getClient } from '@anticrm/presentation'
import DueDatePopup from './DueDatePopup.svelte'
import tracker from '../../plugin'
export let value: Issue
export let currentSpace: Ref<Team> | undefined = undefined
const WARNING_DAYS = 7
const client = getClient()
$: today = new Date(new Date(Date.now()).setHours(0, 0, 0, 0))
$: dueDateMs = value.dueDate
$: isOverdue = dueDateMs !== null && dueDateMs < today.getTime()
$: dueDate = dueDateMs === null ? null : new Date(dueDateMs)
$: daysDifference = dueDate === null ? null : getDaysDifference(today, dueDate)
$: iconModifier = getIconModifier(isOverdue, daysDifference)
$: formattedDate = !dueDateMs ? '' : new Date(dueDateMs).toLocaleString('default', { month: 'short', day: 'numeric' })
const handleDueDateChanged = async (event: CustomEvent<Timestamp>) => {
const newDate = event.detail
if (newDate === undefined) {
return
}
const currentIssue = await client.findOne(tracker.class.Issue, { space: currentSpace, _id: value._id })
if (currentIssue === undefined) {
return
}
await client.update(currentIssue, { dueDate: newDate })
}
const getIconModifier = (isOverdue: boolean, daysDifference: number | null) => {
if (isOverdue) {
return 'overdue' as 'overdue' // Fixes `DatePresenter` icon type issue
}
if (daysDifference === 0) {
return 'critical' as 'critical'
}
if (daysDifference !== null && daysDifference <= WARNING_DAYS) {
return 'warning' as 'warning'
}
}
$: shouldRenderPresenter = dueDateMs && value.status !== IssueStatus.Done && value.status !== IssueStatus.Canceled
</script>
{#if shouldRenderPresenter}
<Tooltip
direction={'top'}
component={DueDatePopup}
props={{
formattedDate: formattedDate,
daysDifference: daysDifference,
isOverdue: isOverdue,
iconModifier: iconModifier
}}
>
<DatePresenter
value={dueDateMs}
editable={true}
shouldShowLabel={false}
icon={iconModifier}
on:change={handleDueDateChanged}
/>
</Tooltip>
{/if}

View File

@ -16,14 +16,16 @@
import { Class, Doc, DocumentQuery, FindOptions, Ref, getObjectValue } from '@anticrm/core'
import { SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { CheckBox, Loading, showPopup, Spinner, IconMoreV } from '@anticrm/ui'
import { CheckBox, Loading, showPopup, Spinner, IconMoreV, Tooltip } from '@anticrm/ui'
import { BuildModelKey } from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import { buildModel, LoadingProps, Menu } from '@anticrm/view-resources'
import tracker from '../../plugin'
export let _class: Ref<Class<Doc>>
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
export let config: (BuildModelKey | string)[]
export let leftItemsConfig: (BuildModelKey | string)[]
export let rightItemsConfig: (BuildModelKey | string)[] | undefined = undefined
export let options: FindOptions<Doc> | undefined = undefined
export let query: DocumentQuery<Doc>
@ -92,13 +94,20 @@
return props.length
}
const buildItemModels = async () => {
const leftModels = await buildModel({ client, _class, keys: leftItemsConfig, options })
const rightModels = rightItemsConfig && (await buildModel({ client, _class, keys: rightItemsConfig, options }))
return { leftModels, rightModels }
}
</script>
{#await buildModel({ client, _class, keys: config, options })}
{#await buildItemModels()}
{#if !isLoading}
<Loading />
{/if}
{:then attributeModels}
{:then itemModels}
<div class="listRoot">
{#if docObjects}
{#each docObjects as docObject, rowIndex (docObject._id)}
@ -107,65 +116,80 @@
class:mListGridChecked={selectedIssueIds.has(docObject._id)}
class:mListGridFixed={rowIndex === selectedRowIndex}
>
{#each attributeModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<div class="eListGridCheckBox ml-2">
<CheckBox
checked={selectedIssueIds.has(docObject._id)}
on:value={(event) => {
handleIssueSelected(docObject._id, event)
}}
/>
<div class="modelsContainer">
{#each itemModels.leftModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<Tooltip direction={'bottom'} label={tracker.string.SelectIssue}>
<div class="eListGridCheckBox ml-2">
<CheckBox
checked={selectedIssueIds.has(docObject._id)}
on:value={(event) => {
handleIssueSelected(docObject._id, event)
}}
/>
</div>
</Tooltip>
<div class="priorityPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
<div class="priorityPresenter">
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
<div
id="context-menu"
class="eIssuePresenterContextMenu"
on:click={(event) => showMenu(event, docObject, rowIndex)}
>
<IconMoreV size={'small'} />
</div>
</div>
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
<div
id="context-menu"
class="eIssuePresenterContextMenu"
on:click={(event) => showMenu(event, docObject, rowIndex)}
>
<IconMoreV size={'small'} />
{/if}
{/each}
</div>
{#if itemModels.rightModels}
<div class="modelsContainer">
{#each itemModels.rightModels as attributeModel}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
</div>
{:else}
<div class="gridElement">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
{/if}
{/each}
{/each}
</div>
{/if}
</div>
{/each}
{:else if loadingProps !== undefined}
{#each Array(getLoadingElementsLength(loadingProps, options)) as _, rowIndex}
<div class="listGrid mListGridIsLoading" class:fixed={rowIndex === selectedRowIndex}>
{#each attributeModels as _, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
<div class="modelsContainer">
<div class="gridElement">
<CheckBox checked={false} />
<div class="ml-4">
<Spinner size="small" />
</div>
{/if}
{/each}
</div>
</div>
</div>
{/each}
{/if}
@ -182,8 +206,9 @@
}
.listGrid {
display: grid;
grid-template-columns: 4rem 5rem 2rem auto 4rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
@ -203,7 +228,7 @@
}
&.mListGridIsLoading {
grid-template-columns: auto;
justify-content: flex-start;
}
&:hover {
@ -225,24 +250,20 @@
}
}
.checkBox {
.modelsContainer {
display: flex;
align-items: center;
justify-content: center;
padding: 0.03rem;
border-radius: 0.25rem;
background-color: rgba(247, 248, 248, 0.5);
opacity: 0;
&:hover {
opacity: 1;
}
}
.gridElement {
display: flex;
align-items: center;
justify-content: start;
margin-left: 0.5rem;
&:first-child {
margin-left: 0;
}
}
.priorityPresenter {
@ -252,7 +273,7 @@
.issuePresenter {
display: flex;
align-items: center;
padding-right: 1rem;
margin-left: 0.5rem;
.eIssuePresenterContextMenu {
visibility: hidden;

View File

@ -16,6 +16,7 @@
import { Ref } from '@anticrm/core'
import { Issue, IssuePriority, Team } from '@anticrm/tracker'
import { getClient } from '@anticrm/presentation'
import { Tooltip } from '@anticrm/ui'
import tracker from '../../plugin'
import PrioritySelector from '../PrioritySelector.svelte'
@ -40,10 +41,12 @@
</script>
{#if value}
<PrioritySelector
kind={'icon'}
shouldShowLabel={false}
priority={value.priority}
onPriorityChange={handlePriorityChanged}
/>
<Tooltip direction={'bottom'} label={tracker.string.SetPriority}>
<PrioritySelector
kind={'icon'}
shouldShowLabel={false}
priority={value.priority}
onPriorityChange={handlePriorityChanged}
/>
</Tooltip>
{/if}

View File

@ -16,6 +16,7 @@
import { Ref } from '@anticrm/core'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { getClient } from '@anticrm/presentation'
import { Tooltip } from '@anticrm/ui'
import tracker from '../../plugin'
import StatusSelector from '../StatusSelector.svelte'
@ -40,5 +41,7 @@
</script>
{#if value}
<StatusSelector kind={'icon'} shouldShowLabel={false} status={value.status} onStatusChange={handleStatusChanged} />
<Tooltip direction={'bottom'} label={tracker.string.SetStatus}>
<StatusSelector kind={'icon'} shouldShowLabel={false} status={value.status} onStatusChange={handleStatusChanged} />
</Tooltip>
{/if}

View File

@ -29,6 +29,7 @@ import IssuePresenter from './components/issues/IssuePresenter.svelte'
import TitlePresenter from './components/issues/TitlePresenter.svelte'
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
import StatusPresenter from './components/issues/StatusPresenter.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
@ -52,6 +53,7 @@ export default async (): Promise<Resources> => ({
PriorityPresenter,
StatusPresenter,
AssigneePresenter,
DueDatePresenter,
EditIssue,
NewIssueHeader
}

View File

@ -40,6 +40,7 @@ export default mergeIds(trackerId, tracker, {
AddIssue: '' as IntlString,
NewIssue: '' as IntlString,
Team: '' as IntlString,
SelectIssue: '' as IntlString,
SelectTeam: '' as IntlString,
SaveIssue: '' as IntlString,
Todo: '' as IntlString,
@ -76,6 +77,10 @@ export default mergeIds(trackerId, tracker, {
DocumentIcon: '' as IntlString,
DocumentColor: '' as IntlString,
Rank: '' as IntlString,
DueDatePopupTitle: '' as IntlString,
DueDatePopupOverdueTitle: '' as IntlString,
DueDatePopupDescription: '' as IntlString,
DueDatePopupOverdueDescription: '' as IntlString,
IssueTitlePlaceholder: '' as IntlString,
IssueDescriptionPlaceholder: '' as IntlString,
@ -98,6 +103,7 @@ export default mergeIds(trackerId, tracker, {
PriorityPresenter: '' as AnyComponent,
StatusPresenter: '' as AnyComponent,
AssigneePresenter: '' as AnyComponent,
DueDatePresenter: '' as AnyComponent,
EditIssue: '' as AnyComponent,
CreateTeam: '' as AnyComponent,
NewIssueHeader: '' as AnyComponent