mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-26 13:47:26 +03:00
Tracker: Issue List - DueDate presenter (#1393)
This commit is contained in:
parent
4ba579b517
commit
e83ffd3804
@ -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()
|
||||
|
||||
|
@ -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 () => { }
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 })
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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": {}
|
||||
}
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 } }
|
||||
]}
|
||||
|
@ -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>
|
@ -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}
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user