New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Card Due Dates #138

Merged
merged 1 commit into from Jan 5, 2017
Jump to file or symbol
Failed to load files and symbols.
+305 −19
Diff settings

Always

Just for now

@@ -315,7 +315,7 @@
font-weight: 400
font-size: 14px
> pre
margin: 0
margin: 0 0 4px
font-size: 14px
font-family: inherit
white-space: pre-wrap
@@ -9,6 +9,8 @@ import ConfirmationLink from '../ConfirmationLink'
import EditCardForm from './EditCardForm'
import CardLabel from './Card/CardLabel'
import commands from '../../commands'
import Badge from './Card/Badge'
import moment from 'moment'
export default class Card extends Component {
static contextTypes = {
@@ -81,6 +83,10 @@ export default class Card extends Component {
style
} = this.props
const dueDateBadge = card.due_date
? <Badge card={card} shownOn='front'/>
: null
let cardLabels = !board ? null : card.label_ids
.map( labelId => board.labels.find(label => label.id === labelId))
.map( label =>
@@ -131,6 +137,7 @@ export default class Card extends Component {
{cardLabels}
</div>
<pre>{card.content}</pre>
{dueDateBadge}
{archivedFooter}
</Link>
<div className="BoardShowPage-Card-controls">
@@ -0,0 +1,71 @@
import React, {Component} from 'react'
import Link from '../../Link'
import Icon from '../../Icon'
import moment from 'moment'
import PopoverMenuButton from '../../PopoverMenuButton'
import DueDatePopover from './DueDatePopover'
import './Badge.sass'
export default class Badge extends Component {
constructor(props){
super(props)
this.dueStatus = this.dueStatus.bind(this)
}
dueStatus() {
const dueDate = moment(this.props.card.due_date)
const pastDue = moment().isAfter(dueDate)
const status = {
className: '',
preText: '',
postText: ''
}
if (pastDue) {
if (dueDate.isBefore(moment().subtract(1, 'days'), 'day')) {
status.className = 'Badge-due-past-long'
status.preText = dueDate.format('MMM D [at] h:mm')
status.postText = '(past due)'
} else {
status.className = 'Badge-due-past-recent'
status.preText = dueDate.calendar(moment(), 'D hh:mm A');
status.postText = '(recently past due)'
}
} else {
if (dueDate.isAfter(moment().add(1, 'days'), 'day')) {
status.className = 'Badge-due-future-distant'
status.preText = dueDate.format('MMM D [at] h:mm')
} else {
status.className = 'Badge-due-future-near'
status.preText = dueDate.calendar(moment(), 'D hh:mm A');
status.postText = '(due soon)'
}
}
if (this.props.card.complete){

This comment has been minimized.

@deadlyicon

deadlyicon Dec 20, 2016

Contributor

can e do this check first so we can save all the other calculations?

@deadlyicon

deadlyicon Dec 20, 2016

Contributor

can e do this check first so we can save all the other calculations?

This comment has been minimized.

@mantinone

mantinone Dec 20, 2016

Collaborator

We need to check if the due date is within one day of the current time so we can display "today", "tomorrow", or "yesterday" instead of the date, so running through the previous checks seems the simplest way to avoid repeating ourselves.

@mantinone

mantinone Dec 20, 2016

Collaborator

We need to check if the due date is within one day of the current time so we can display "today", "tomorrow", or "yesterday" instead of the date, so running through the previous checks seems the simplest way to avoid repeating ourselves.

This comment has been minimized.

@deadlyicon

deadlyicon Dec 21, 2016

Contributor

doesn't moment have a .fromNow() method? can we just use that?

@deadlyicon

deadlyicon Dec 21, 2016

Contributor

doesn't moment have a .fromNow() method? can we just use that?

This comment has been minimized.

@mantinone

mantinone Dec 22, 2016

Collaborator

fromNow() returns a string that says how many hours/days/years away something is, and doesn't provide any method for telling it what level of resolution you want. As far as I can see, isBefore() and isAfter() are the only ways of checking if a date falls within a certain relative range, and calendar() is the thing that will give us "today", "tomorrow", or "yesterday"

I can't think of any way to handle the today tomorrow yesterday thing without essentially repeating a lot of the if-else logic above that part.

@mantinone

mantinone Dec 22, 2016

Collaborator

fromNow() returns a string that says how many hours/days/years away something is, and doesn't provide any method for telling it what level of resolution you want. As far as I can see, isBefore() and isAfter() are the only ways of checking if a date falls within a certain relative range, and calendar() is the thing that will give us "today", "tomorrow", or "yesterday"

I can't think of any way to handle the today tomorrow yesterday thing without essentially repeating a lot of the if-else logic above that part.

This comment has been minimized.

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

fromNow() returns a string that says how many hours/days/years away something is, and doesn't provide any method for telling it what level of resolution you want. As far as I can see, isBefore() and isAfter() are the only ways of checking if a date falls within a certain relative range, and calendar() is the thing that will give us "today", "tomorrow", or "yesterday"

I can't think of any way to handle the today tomorrow yesterday thing without essentially repeating a lot of the if-else logic above that part.
@mantinone

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

fromNow() returns a string that says how many hours/days/years away something is, and doesn't provide any method for telling it what level of resolution you want. As far as I can see, isBefore() and isAfter() are the only ways of checking if a date falls within a certain relative range, and calendar() is the thing that will give us "today", "tomorrow", or "yesterday"

I can't think of any way to handle the today tomorrow yesterday thing without essentially repeating a lot of the if-else logic above that part.
@mantinone

This comment has been minimized.

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

okay as long as you've investigated using moment to achieve this. :)

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

okay as long as you've investigated using moment to achieve this. :)

status.className = 'Badge-due-complete'
status.postText = ''
}
return status
}
render(){
const { card, shownOn } = this.props
let status = this.dueStatus()
let className = `Badge-due ${status.className || ''}`
let renderBadge
if (shownOn === 'front'){
let shortDate = moment(card.due_date).format('MMM D')
return <div className={className}><Icon type="clock-o" className="Badge-due-dueIcon"/><span className="Badge-due-dueText">{shortDate}</span></div>
}
let longDate = status.preText
const dueDatePopover = <DueDatePopover card={card}/>
className += ' Badge-due-large'
return <div className='Badge'>
<div className='Badge-header'>Due Date</div>
<PopoverMenuButton key={card.id} type='unstyled' popover={dueDatePopover} className='Badges-labels-Label Badge-buttonHack'>
<div className={className}><span className="Badge-due-dueText">{longDate + ' ' + status.postText}</span></div>
</PopoverMenuButton>
</div>
}
}
@@ -0,0 +1,45 @@
@import "../../../style.sass"

This comment has been minimized.

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

perfect

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

perfect

.Badge
margin: 4px 28px 16px 0
&-buttonHack
margin-top: 7px
&-due
border-radius: 3px
display: inline
color: white
padding: 1px 2px
margin: 0 4px 4px 0
&-large
font-size: 80%
padding: 0.4em 0.5em
font-weight: bolder
&-dueText, &-dueIcon
padding: 3px
&-dueText
font-size: 12px
&-complete
background-color: $green
&-past
&-long
background-color: $alert-red
&-recent
background-color: $danger-red
&-future
&-distant
background-color: transparent
color: $medium-grey
&-near
background-color: $gold
&-header
color: #999999
font-weight: normal
font-size: .89em
margin: 10px 0 5px
&-labels
display: flex
flex-direction: row
flex-wrap: wrap
&-Label
margin-right: 0.25em
@@ -2,6 +2,7 @@ import React, { Component } from 'react'
import './CardModal.sass'
import moment from 'moment'
import LabelMenu from './LabelMenu'
import DueDatePopover from './DueDatePopover'
import CardLabel from './CardLabel'
import Card from '../Card'
import Link from '../../Link'
@@ -17,6 +18,7 @@ import PopoverMenuButton from '../../PopoverMenuButton'
import CopyCard from '../CopyCard'
import Activity from '../../Activity'
import commands from '../../../commands'
import Badge from './Badge'
export default class CardModal extends Component {
static propTypes = {
@@ -70,15 +72,20 @@ export default class CardModal extends Component {
board={board}
/>
let dueDateBadge = card.due_date ? <Badge card={card} shownOn='back'/>: null
return <div className="CardModal">
<CardModalShroud onClose={this.props.onClose}>
{archivedBanner}
<div className="CardModal-columns">
<div className="CardModal-content">
<CardHeader card={card} list={list}/>
<div className="CardModal-body">
<CardLabels card={card} board={board} labelPanel={labelPanel}/>
<CardDescription card={card} />
<div className="CardModal-badges">
<CardLabels card={card} board={board} labelPanel={labelPanel}/>
{dueDateBadge}
</div>
<CardDescription card={card}/>
</div>
<CardCommentForm card={card} session={session}/>
<CardActivity board={board} card={card}/>
@@ -132,7 +139,7 @@ const CardLabels =({card, board, labelPanel}) => {
.map(label =>
<PopoverMenuButton
key={label.id}
className="CardModal-CardLabels-labels-Label"
className="CardModal-CardBadges-labels-Label"
type="unstyled"
popover={labelPanel}
>
@@ -144,17 +151,18 @@ const CardLabels =({card, board, labelPanel}) => {
</PopoverMenuButton>
)
const labelHeader = cardLabels.length > 0 ?
<div className="CardModal-CardLabels-header">Labels</div> : null
<div className="CardModal-CardBadges-header">Labels</div> : null
return <div className="CardModal-CardLabels">
return <div className="CardModal-CardBadges">
{labelHeader}
<div className="CardModal-CardLabels-labels">
<div className="CardModal-CardBadges-labels">
{cardLabels}
</div>
</div>
}
const Controls = ({board, list, card, closeModal, labelPanel}) => {
const dueDate = <DueDatePopover card={card}/>
const copyCard = <CopyCard card={card} board={board} list={list}/>
const toggleOnArchived = card.archived ?
<div>
@@ -169,6 +177,9 @@ const Controls = ({board, list, card, closeModal, labelPanel}) => {
<PopoverMenuButton className="CardModal-Controls-label" type="default" popover={labelPanel}>
<Icon type="tag" /> Labels
</PopoverMenuButton>
<PopoverMenuButton className="CardModal-Controls-label" type="default" popover={dueDate}>
<Icon type="clock-o" /> Due Date
</PopoverMenuButton>
<div className="CardModal-Controls-title">Actions</div>
{toggleOnArchived}
<PopoverMenuButton className="CardModal-Controls-copy" type="default" popover={copyCard}>
@@ -22,6 +22,8 @@
margin-left: 35px
display: flex
flex-direction: column
&-badges
display: flex
&-CardModalShroud
&-shroud, &-stage
@@ -83,7 +85,8 @@
input:focus
outline: none
&-CardLabels
&-CardBadges
margin: 4px 28px 16px 0
&-header
color: #999999
font-weight: normal
@@ -0,0 +1,28 @@
@import "../../../style.sass"
.Dialog
&-dueDateForm
font-weight: bold
padding: 10px
align-items: flex-start
&:last-child
position: absolute
bottom: 0
right: 0
padding: 0
margin: 0 10px 20px 0
&-inputRow
display: flex
flex-direction: row
justify-content: space-between
width: 100%
&-inputLabel
flex-grow: 1
&:first-child
margin-right: 12px
&-textInput
font: 14px "Helvetica Neue",Arial,Helvetica,sans-serif
&:focus
background-color: white
border-color: $blue
box-shadow: 0 0 2px $blue
@@ -0,0 +1,106 @@
import React, {Component} from 'react'
import './Dialog.sass'
import Form from '../../Form'
import Button from '../../Button'
import DialogBox from '../../DialogBox'
import commands from '../../../commands'
import moment from 'moment'
export default class DueDatePopover extends Component {
constructor(props){
super(props)
this.dateOnChange = this.dateOnChange.bind(this)

This comment has been minimized.

@deadlyicon

deadlyicon Jan 4, 2017

Contributor

please make sure we are only binding the methods that are being given as callbacks. For example I dont think initialState needs binding

@deadlyicon

deadlyicon Jan 4, 2017

Contributor

please make sure we are only binding the methods that are being given as callbacks. For example I dont think initialState needs binding

this.dateOnBlur = this.dateOnBlur.bind(this)
this.timeOnChange = this.timeOnChange.bind(this)
this.timeOnBlur = this.timeOnBlur.bind(this)
this.addDueDate = this.addDueDate.bind(this)
this.removeDueDate = this.removeDueDate.bind(this)
this.state = this.initialState()
}
defaultDueDate(){
return moment().endOf('day').add(12, 'hours').add(1, 'minutes')
}
initialDueDate(){
return this.props.card.due_date
? moment(this.props.card.due_date)
: this.defaultDueDate()
}
dueDateToState(due_date){
return {
date: due_date.format('MM/DD/YYYY'),
time: due_date.format('hh:mm a'),
}
}
initialState(){
return this.dueDateToState(this.initialDueDate())
}
validateCurrentDueDate(){
const { date, time } = this.state
let due_date = moment(`${date} ${time}`)
return due_date.isValid() ? due_date : this.initialDueDate()
}
setValidDueDate(){
this.setState(this.dueDateToState(this.validateCurrentDueDate()))
}
addDueDate(event){
event.preventDefault()
this.updateCardDueDate( this.validateCurrentDueDate() )
}
removeDueDate(event){
event.preventDefault()
this.updateCardDueDate( null )
}
updateCardDueDate( due_date ){
commands.updateCard( this.props.card.id, { due_date } )
.then( () => {
boardStore.reload()
this.props.onClose()
})
}
dateOnChange(event){
this.setState({date: event.target.value})
}
dateOnBlur(event){
this.setValidDueDate()
}
timeOnChange(event){
this.setState({time: event.target.value})
}
timeOnBlur(event){
this.setValidDueDate()
}
render (){
return <DialogBox className="Dialog" heading='Change Due Date' onClose={this.props.onClose}>
<Form className='Dialog-dueDateForm' onSubmit={this.addDueDate} name='add'>
<div className="Dialog-inputRow">
<label className="Dialog-inputLabel">Date<input type='text' className="Dialog-inputText" value={this.state.date} onChange={this.dateOnChange} onBlur={this.dateOnBlur}></input></label>
<label className="Dialog-inputLabel">Time<input type='text' className="Dialog-inputText" value={this.state.time} onChange={this.timeOnChange} onBlur={this.timeOnBlur}></input></label>
</div>
<p>There will be a calendar and such here. Perhaps its own component?</p>
<div className="Dialog-inputRow">
<Button type='primary' submit>Save</Button>
</div>
</Form>
<Form className='Dialog-dueDateForm' onSubmit={this.removeDueDate} name='delete'>
<Button submit type='danger'>Remove</Button>
</Form>
</DialogBox>
}
}

This comment has been minimized.

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

ok so having read through all of your logic I was thinking this kind of a structure might read cleaner https://gist.github.com/deadlyicon/723a49e91fab2c6a348844b341a46ac0 I have not tested this but I wrote it so you could hopefully see my thinking. I tried to dry things up as much as I could.

@deadlyicon

deadlyicon Dec 22, 2016

Contributor

ok so having read through all of your logic I was thinking this kind of a structure might read cleaner https://gist.github.com/deadlyicon/723a49e91fab2c6a348844b341a46ac0 I have not tested this but I wrote it so you could hopefully see my thinking. I tried to dry things up as much as I could.

This comment has been minimized.

@deadlyicon

deadlyicon Jan 4, 2017

Contributor

did you check this out?

@deadlyicon

deadlyicon Jan 4, 2017

Contributor

did you check this out?

This comment has been minimized.

@mantinone

mantinone Jan 4, 2017

Collaborator

Yes. We refactored a lot of DueDatePopover so it uses most of your example code here, with a few adjustments.

@mantinone

mantinone Jan 4, 2017

Collaborator

Yes. We refactored a lot of DueDatePopover so it uses most of your example code here, with a few adjustments.

Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.