diff --git a/api/models/index.js b/api/models/index.js
index 39b2a370..6d9bf9e4 100644
--- a/api/models/index.js
+++ b/api/models/index.js
@@ -56,7 +56,7 @@ const associations = ({ Allocation, Client, Contributor, Issue, Payment, Permiss
Contributor.hasMany(Permission, { foreignKey: 'contributor_id' })
Payment.hasMany(Allocation, { foreignKey: 'payment_id' })
Project.hasMany(Allocation, { foreignKey: 'project_id' })
- Project.belongsTo(Client, { foreignKey: 'client_id' })
+ Client.hasMany(Project, { foreignKey: 'client_id' })
Project.hasMany(Permission, { foreignKey: 'project_id' })
Issue.belongsTo(Project, { foreignKey: 'project_id' })
Rate.hasMany(Allocation, { foreignKey: 'rate_id' })
diff --git a/api/modules/dataSyncs.js b/api/modules/dataSyncs.js
index 3a9a01c1..a82f539e 100644
--- a/api/modules/dataSyncs.js
+++ b/api/modules/dataSyncs.js
@@ -93,8 +93,6 @@ const dataSyncs = module.exports = (() => {
const syncInvoicelyCSV = async () => {
const invoiceFile = INVOICELY_CSV_PATH
- console.log('invoiceFile')
- console.log(invoiceFile)
return (
amazon.fetchFile({ file: invoiceFile })
.then(file => {
diff --git a/api/schema/resolvers/ContributorResolver.js b/api/schema/resolvers/ContributorResolver.js
index a3e21f0b..33f70eb2 100644
--- a/api/schema/resolvers/ContributorResolver.js
+++ b/api/schema/resolvers/ContributorResolver.js
@@ -1,8 +1,39 @@
const { AuthenticationError } = require('apollo-server')
+const { col, fn } = require('sequelize')
+
const toggl = require('../../handlers/toggl')
module.exports = {
Contributor: {
+ paidByCurrency: async (contributor, args, { models }) => {
+ const totalPaidByCurrencyQuery = await models.Client.findAll({
+ group: 'currency',
+ attributes: ['currency'],
+ raw: true,
+ include: {
+ model: models.Project,
+ attributes: [],
+ required: true,
+ include: {
+ model: models.Allocation,
+ attributes: [[fn('sum', col('amount')), 'amount']],
+ where: {
+ contributor_id: contributor.id
+ },
+ }
+ }
+ })
+ totalPaidByCurrency = []
+ totalPaidByCurrencyQuery.map(t => {
+ totalPaidByCurrency.push(
+ {
+ currency: t['currency'],
+ amount: t['Projects.Allocations.amount']
+ }
+ )
+ })
+ return totalPaidByCurrency
+ },
permissions: (contributor, args, { models }) => {
return models.Permission.findAll({
where: {
@@ -17,7 +48,7 @@ module.exports = {
}
})
},
- total_paid: async (contributor, args, { models }) => {
+ totalPaid: async (contributor, args, { models }) => {
const totalPaid = await models.Allocation.sum('amount', {
where: {
contributor_id: contributor.id
diff --git a/api/schema/resolvers/ProjectResolver.js b/api/schema/resolvers/ProjectResolver.js
index 2a41ab06..26a6c60c 100644
--- a/api/schema/resolvers/ProjectResolver.js
+++ b/api/schema/resolvers/ProjectResolver.js
@@ -1,7 +1,7 @@
const moment = require('moment')
-const sequelize = require('sequelize');
+const sequelize = require('sequelize')
const { ApolloError } = require('apollo-server')
-const { col, fn, Op } = require('sequelize');
+const { col, fn, Op } = require('sequelize')
const { split } = require('lodash')
const { GITHUB, TOGGL } = require('../../config/credentials');
@@ -9,7 +9,7 @@ const github = require('../../handlers/github')
const toggl = require('../../handlers/toggl')
const { validateDatesFormat } = require('../helpers/inputValidation')
const { dataSyncs } = require('../../modules')
-const apiModules = require('../../modules');
+const apiModules = require('../../modules')
module.exports = {
@@ -132,7 +132,7 @@ module.exports = {
}
) || 0
return {
- fromPayments: parseInt( totalPaidFromClient / totalIssues, 10),
+ fromPayments: parseInt(totalPaidFromClient / totalIssues, 10),
fromAllocations: parseInt(totalPaidFromAllocation / totalIssues, 10)
}
},
diff --git a/api/schema/types/ContributorType.js b/api/schema/types/ContributorType.js
index 63637d44..1789e075 100644
--- a/api/schema/types/ContributorType.js
+++ b/api/schema/types/ContributorType.js
@@ -5,7 +5,6 @@ module.exports = gql`
type Contributor {
id: Int!
toggl_id: Int
- total_paid: Int!
name: String!
external_data_url: String
github_id: String
@@ -15,6 +14,9 @@ module.exports = gql`
permissions: [Permission]
timeEntries: [TimeEntry]
rates: [Rate]
+ "The following attributes are calculated and aren't on the database"
+ totalPaid: Int!
+ paidByCurrency: [TotalAllocatedByCurrency]
}
input CreateContributorInput {
@@ -38,6 +40,11 @@ module.exports = gql`
github_handle: String
}
+ type TotalAllocatedByCurrency {
+ amount: Int
+ currency: String
+ }
+
type Query {
checkSession: Contributor
getContributorById(id: Int!): Contributor
diff --git a/api/schema/types/ProjectType.js b/api/schema/types/ProjectType.js
index 154b582e..484aa929 100644
--- a/api/schema/types/ProjectType.js
+++ b/api/schema/types/ProjectType.js
@@ -54,10 +54,11 @@ module.exports = gql`
toDate: String,
confirmedOnly: Boolean
): Int
+ "The total paid from the client"
totalPaid(
fromDate: String,
toDate: String
- ): Int # The total paid from the client, takes start date and ed date as attributes
+ ): Int
totalIncurredPayments: Int # The total incurred not paid from the client
}
diff --git a/src/components/AllocationAddForm.js b/src/components/AllocationAddForm.js
index edcd2e90..63e91be5 100644
--- a/src/components/AllocationAddForm.js
+++ b/src/components/AllocationAddForm.js
@@ -108,7 +108,7 @@ const AllocationAddForm = (props) => {
rate_id: allocationRate.id
}
})
- if (loadingNewAllocation) return loading...
+ if (loadingNewAllocation) return ''
else if (allocationCreated.errors) {
console.log('Error adding the allocation');
} else {
@@ -231,17 +231,6 @@ const AllocationAddForm = (props) => {
variables: {
contributorId: contributor ? contributor.id : null,
projectId: project ? project.id : null
-
- }
- }, {
- query: GET_PAYMENT_ALLOCATIONS,
- variables: {
- paymentId: selectedPayment ? selectedPayment.id : null
- }
- }, {
- query: GET_PAYMENT_TOTAL_ALLOCATED,
- variables: {
- paymentId: selectedPayment ? selectedPayment.id : null
}
}, {
query: GET_PROJECT_PAYMENTS,
@@ -370,8 +359,7 @@ const AllocationAddForm = (props) => {
const activeContributors = activeAllocations.map(a => {
return a.contributor
})
-
- if (!rateCurrency) {
+ if (!rateCurrency && clientCurrency) {
setRateCurrency(clientCurrency)
}
@@ -495,6 +483,7 @@ const AllocationAddForm = (props) => {
{
{
- {
- c.github_access_token &&
+ {c.github_access_token &&
}
diff --git a/src/components/AllocationOverview.js b/src/components/AllocationOverview.js
new file mode 100644
index 00000000..156ab59a
--- /dev/null
+++ b/src/components/AllocationOverview.js
@@ -0,0 +1,290 @@
+import React, { useEffect, useState } from 'react'
+import { useMutation, useQuery, useLazyQuery } from '@apollo/client'
+import {
+ Box,
+ Button,
+ Dialog,
+ DialogTitle,
+ Grid,
+ Typography
+} from '@material-ui/core/'
+import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'
+import moment from 'moment'
+import { findKey } from 'lodash'
+
+import DeleteConfirmationDialog from './DeleteConfirmationDialog'
+import EditAllocationInfo from './EditAllocationInfo'
+import EditAllocationRate from './EditAllocationRate'
+import LoadingProgress from './LoadingProgress'
+import { GET_ALLOCATIONS, GET_ALLOCATION_INFO } from '../operations/queries/AllocationQueries'
+import { GET_CLIENT_PAYMENTS } from '../operations/queries/ClientQueries'
+import { GET_CONTRIBUTORS, GET_CONTRIBUTOR_ALLOCATIONS, GET_CONTRIBUTOR_RATES } from '../operations/queries/ContributorQueries'
+import { GET_PAYMENT_ALLOCATIONS } from '../operations/queries/PaymentQueries'
+import { GET_PROJECT_CONTRIBUTORS, GET_PROJECT_PAYMENTS } from '../operations/queries/ProjectQueries'
+import { DELETE_ALLOCATION, UPDATE_ALLOCATION } from '../operations/mutations/AllocationMutations'
+import { CREATE_RATE } from '../operations/mutations/RateMutations'
+
+const AllocationOverview = (props) => {
+
+ const {
+ allocationInfo,
+ onClose,
+ open
+ } = props
+
+ const [clientPayments, setClientPayments] = useState(null)
+ const [contributorAllocation, setContributorAllocation] = useState(null)
+ const [contributorRates, setContributorRates] = useState(null)
+ const [updatedAllocationPayment, setUpdatedAllocationPayment] = useState(null)
+ const [updatedAllocationRate, setUpdatedAllocationRate] = useState({})
+ const [openDeleteAllocation, setOpenDeleteAllocation] = useState(false)
+ const [selectedCurrency, setSelectedCurrency] = useState(null)
+ const [updatedAllocationStartDate, setUpdatedAllocationStartDate] = useState(null)
+ const [updatedAllocationEndDate, setUpdatedAllocationEndDate] = useState(null)
+
+ const {
+ data: dataAllocation,
+ error: errorAllocation,
+ loading: loadingAllocation
+ } = useQuery(GET_ALLOCATION_INFO, {
+ fetchPolicy: 'cache-and-network',
+ variables: {
+ id: allocationInfo.id
+ }
+ })
+
+ const [getClientPayments, {
+ data: dataClientPayments,
+ loading: loadingClientPayments,
+ error: errorClientPayments
+ }] = useLazyQuery(GET_CLIENT_PAYMENTS, {
+ onCompleted: dataClientPayments => {
+ setClientPayments(dataClientPayments.getClientById)
+ }
+ })
+
+ const [getContributorRates, {
+ data: dataContributorRates,
+ loading: loadingContributorRates,
+ error: errorContributorRates
+ }] = useLazyQuery(GET_CONTRIBUTOR_RATES, {
+ onCompleted: dataContributorRates => {
+ setContributorRates(dataContributorRates.getContributorById)
+ }
+ })
+
+ const [createRate, {
+ dataNewRate,
+ loadingNewRate,
+ errorNewRate
+ }] = useMutation(CREATE_RATE)
+
+ const [deleteAllocation, {
+ dataDeletedPayment,
+ loadingDeletedPayment,
+ errorDeletedPayment
+ }] = useMutation(DELETE_ALLOCATION, {
+ variables: {
+ id: allocationInfo.id
+ },
+ refetchQueries: [{
+ query: GET_ALLOCATIONS,
+ variables: {
+ projectId: contributorAllocation ? contributorAllocation.project.id : null,
+ contributorId: contributorAllocation ? contributorAllocation.contributor.id : null
+ }
+ }, {
+ query: GET_PROJECT_CONTRIBUTORS,
+ variables: {
+ id: contributorAllocation ? contributorAllocation.project.id : null
+ }
+ }]
+ })
+
+ const [updateAllocation, {
+ dataUpdatedAllocation,
+ loadingUpdatedAllocation,
+ errorUpdatedAllocation
+ }] = useMutation(UPDATE_ALLOCATION, {
+ refetchQueries: [{
+ query: GET_CONTRIBUTOR_ALLOCATIONS,
+ variables: {
+ id: contributorAllocation ? contributorAllocation.contributor.id : null
+ }
+ }]
+ })
+
+ useEffect(() => {
+ if (contributorAllocation) {
+ getClientPayments({
+ variables: {
+ clientId: contributorAllocation.project.client.id
+ }
+ })
+ getContributorRates({
+ variables: {
+ id: contributorAllocation.contributor.id
+ }
+ })
+ setUpdatedAllocationEndDate(moment(allocation.end_date, 'x')['_d'])
+ setUpdatedAllocationPayment(contributorAllocation.payment)
+ setUpdatedAllocationStartDate(moment(allocation.start_date, 'x')['_d'])
+ }
+ }, [contributorAllocation])
+
+ const handleClose = () => {
+ setContributorAllocation(null)
+ onClose()
+ }
+
+ const handleDeleteAllocation = async () => {
+ const paymentDeleted = await deleteAllocation()
+ onClose()
+ }
+
+ const handleUpdateAllocation = async ({
+ allocation,
+ contributor,
+ contributorRates,
+ endDate,
+ payment,
+ rate,
+ startDate,
+ }) => {
+ //look for rate with same values
+ const selectedRate = {}
+ const existingRate = findKey(
+ contributorRates,
+ {
+ 'hourly_rate': rate.hourly_rate.toString(),
+ 'total_expected_hours': Number(rate.total_expected_hours),
+ 'type': rate.type,
+ 'currency': selectedCurrency
+ }
+ )
+ if (existingRate) {
+ selectedRate.id = contributorRates[existingRate].id
+ } else {
+ //create rate
+ const newRate = await createRate({
+ variables: {
+ hourly_rate: rate.hourly_rate.toString(),
+ total_expected_hours: Number(rate.total_expected_hours),
+ type: rate.type,
+ currency: selectedCurrency,
+ contributor_id: contributor.id
+ }
+ })
+ selectedRate.id = newRate.data.createRate.id
+ }
+ //update allocation with that rate id
+ try {
+ const updatedAllocation = await updateAllocation({
+ variables: {
+ id: allocation.id,
+ amount: Number(rate.total_amount),
+ start_date: moment(startDate).format('YYYY-MM-DD'),
+ end_date: moment(endDate).format('YYYY-MM-DD'),
+ date_paid: null,
+ rate_id: Number(selectedRate.id),
+ payment_id: payment ? payment.id : null
+ }
+ })
+ if (loadingUpdatedAllocation) return ''
+ else if (updatedAllocation.errors) {
+ throw updatedAllocation.errors
+ } else {
+ onClose()
+ }
+ } catch (error) {
+ console.log(`error ${error}`);
+ onClose()
+ }
+
+ }
+
+ if (loadingAllocation || loadingClientPayments) return
+ if (errorAllocation || errorClientPayments) return `Error`
+
+ const { getAllocationById: allocation } = dataAllocation
+ const payments = [null]
+ if (clientPayments) {
+ payments.unshift(...clientPayments.payments)
+ }
+
+ if (!contributorAllocation) {
+ setContributorAllocation(allocation)
+ }
+
+ return (
+
+ )
+}
+
+export default AllocationOverview
diff --git a/src/components/AllocationTile.js b/src/components/AllocationTile.js
index caac03e1..cca77dcf 100644
--- a/src/components/AllocationTile.js
+++ b/src/components/AllocationTile.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState } from 'react'
import {
Box,
Card,
@@ -9,6 +9,7 @@ import AccountBalanceWalletIcon from '@material-ui/icons/AccountBalanceWallet'
import accounting from 'accounting-js'
import moment from 'moment'
+import AllocationOverview from './AllocationOverview'
import {
formatAmount,
selectCurrencyInformation
@@ -21,8 +22,10 @@ const AllocationTile = (props) => {
allocation
} = props
+ const [openAllocationOverview, setOpenAllocationOverview] = useState(false)
+
const currencyInformation = selectCurrencyInformation({
- currency: 'USD'
+ currency: allocation.rate.currency
})
const paymentAmount = formatAmount({
amount: allocation.amount / 100,
@@ -33,8 +36,12 @@ const AllocationTile = (props) => {
return (
-
-
+
+ setOpenAllocationOverview(true)}>
@@ -101,6 +108,11 @@ const AllocationTile = (props) => {
+ setOpenAllocationOverview(false)}
+ open={openAllocationOverview}
+ />
)
diff --git a/src/components/ContributorAllocations.js b/src/components/ContributorAllocations.js
index 30c82cf8..1732e607 100644
--- a/src/components/ContributorAllocations.js
+++ b/src/components/ContributorAllocations.js
@@ -6,11 +6,16 @@ import {
Grid,
Typography
} from '@material-ui/core'
+import { isEmpty } from 'lodash'
import AllocationAddForm from './AllocationAddForm'
import AllocationTile from './AllocationTile'
+import EmptyState from './EmptyState'
import LoadingProgress from './LoadingProgress'
-import { GET_CONTRIBUTOR_ALLOCATIONS, GET_CONTRIBUTOR_INFO } from '../operations/queries/ContributorQueries'
+import {
+ GET_CONTRIBUTOR_ALLOCATIONS,
+ GET_CONTRIBUTOR_INFO
+} from '../operations/queries/ContributorQueries'
import { white } from '../styles/colors.scss'
const ContributorAllocations = (props) => {
@@ -88,7 +93,16 @@ const ContributorAllocations = (props) => {
- {renderAllocations({ allocations: contributorAllocations.allocations })}
+ {
+ !isEmpty(contributorAllocations.allocations)
+ ? renderAllocations({ allocations: contributorAllocations.allocations })
+ : (
+
+ )
+ }
diff --git a/src/components/ContributorInfoTile.js b/src/components/ContributorInfoTile.js
index bee40bf4..a7ce9d81 100644
--- a/src/components/ContributorInfoTile.js
+++ b/src/components/ContributorInfoTile.js
@@ -6,6 +6,11 @@ import {
CardActions,
CardContent,
Grid,
+ Icon,
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
Typography
} from '@material-ui/core'
import CheckCircleIcon from '@material-ui/icons/CheckCircle'
@@ -45,15 +50,39 @@ const ContributorInfoTile = (props) => {
currency: 'USD'
})
const paymentAmount = formatAmount({
- amount: contributor.total_paid / 100,
+ amount: contributor.totalPaid / 100,
currencyInformation: currencyInformation
})
+ const renderPaidToContributorByCurrency = (props) => {
+ const {
+ paidByCurrency
+ } = props
+
+ return paidByCurrency.map(p => {
+ const currencyInformation = selectCurrencyInformation({
+ currency: p.currency
+ })
+ const paymentAmount = formatAmount({
+ amount: p.amount / 100,
+ currencyInformation: currencyInformation
+ })
+ return (
+
+
+
+
+
+
+ )
+ })
+ }
+
return (
-
+
@@ -62,10 +91,7 @@ const ContributorInfoTile = (props) => {
-
+
@@ -76,19 +102,28 @@ const ContributorInfoTile = (props) => {
-
-
- {`Total paid from allocations: ${paymentAmount}`}
-
-
+
+
+
+
+
+ {`Total paid from allocations:`}
+
+
+
+ {renderPaidToContributorByCurrency({ paidByCurrency: contributor.paidByCurrency })}
+
+
+
+
)
diff --git a/src/components/ContributorProjectsCollab.js b/src/components/ContributorProjectsCollab.js
index f61661dc..31553689 100644
--- a/src/components/ContributorProjectsCollab.js
+++ b/src/components/ContributorProjectsCollab.js
@@ -6,9 +6,10 @@ import {
Grid,
Typography
} from '@material-ui/core'
-import { find } from 'lodash'
+import { find, isEmpty } from 'lodash'
import moment from 'moment'
+import EmptyState from './EmptyState'
import LoadingProgress from './LoadingProgress'
import ProjectTile from './ProjectTile'
import { GET_CONTRIBUTOR_PROJECTS } from '../operations/queries/ContributorQueries'
@@ -31,8 +32,6 @@ const ContributorProjectsCollab = (props) => {
const renderProjects = ({ propjects }) => {
return projects.map(p => {
- console.log('p');
- console.log(p);
return (
@@ -68,11 +67,20 @@ const ContributorProjectsCollab = (props) => {
- {`Project Involvment`}
+ {`Project Involvement`}
- {renderProjects({ projects: projects })}
+ {
+ !isEmpty(projects)
+ ? renderProjects({ projects: projects })
+ : (
+
+ )
+ }
diff --git a/src/components/ContributorTile.js b/src/components/ContributorTile.js
index 8453d5c8..62ef8071 100644
--- a/src/components/ContributorTile.js
+++ b/src/components/ContributorTile.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useQuery } from '@apollo/client'
import {
Accordion,
@@ -15,6 +15,7 @@ import moment from 'moment'
import { filter, sortBy } from 'lodash'
import { useHistory } from 'react-router-dom'
+import AllocationOverview from './AllocationOverview'
import { GET_ALLOCATIONS } from '../operations/queries/AllocationQueries'
import {
formatAmount,
@@ -32,9 +33,16 @@ const ContributorTile = (props) => {
} = props
const history = useHistory()
+ const [openAllocationOverview, setOpenAllocationOverview] = useState(false)
+ const [selectedAllocation, setSelectedAllocation] = useState(null)
+
const handleAddButton = () => {
onAddButton({ contributor })
}
+ const handleAllocationOverview = ({ value, allocation }) => {
+ setSelectedAllocation(allocation)
+ setOpenAllocationOverview(value)
+ }
const redirectToContributorDetails = ({ contributor }) => {
history.push(`/contributor/${contributor.id}`)
}
@@ -65,7 +73,7 @@ const ContributorTile = (props) => {
return sortedAllocations.map(a => {
const currencyInformation = selectCurrencyInformation({
- currency: a.payment ? a.payment.client.currency : ''
+ currency: a.rate.currency
})
const allocationAmount = formatAmount({
amount: a.amount / 100,
@@ -78,7 +86,11 @@ const ContributorTile = (props) => {
const isActiveAllocation = moment(a.start_date, 'x').isBefore(moment())
return (
-
+ handleAllocationOverview({ value: true, allocation: a })}
+ >
@@ -234,6 +246,13 @@ const ContributorTile = (props) => {
+ {selectedAllocation &&
+ handleAllocationOverview({ value: false })}
+ open={openAllocationOverview}
+ />
+ }
)
diff --git a/src/components/DeletePayment.js b/src/components/DeletePayment.js
index 41a28e18..a01f9d3d 100644
--- a/src/components/DeletePayment.js
+++ b/src/components/DeletePayment.js
@@ -44,7 +44,6 @@ const DeletePayment = (props) => {
handleDeletePayment()}
deleteItem={`payment`}
- payment={payment}
open={open}
onClose={onClose}
/>
diff --git a/src/components/EditAllocationInfo.js b/src/components/EditAllocationInfo.js
new file mode 100644
index 00000000..099b2834
--- /dev/null
+++ b/src/components/EditAllocationInfo.js
@@ -0,0 +1,173 @@
+import React, { useEffect, useState } from 'react'
+import {
+ Collapse,
+ Grid,
+ List,
+ ListItem,
+ ListItemText,
+ Typography
+} from '@material-ui/core/'
+import PaymentIcon from '@material-ui/icons/Payment'
+import {
+ ExpandLess,
+ ExpandMore
+} from '@material-ui/icons'
+import moment from 'moment'
+import {
+ differenceWith,
+ findIndex,
+ isEqual
+} from 'lodash'
+
+import { formatAmount, selectCurrencyInformation } from '../scripts/selectors'
+
+const EditAllocationInfo = (props) => {
+
+ const {
+ allocation,
+ payments,
+ selectedPayment,
+ setSelectedPayment
+ } = props
+
+ const currencyInformation = selectCurrencyInformation({
+ currency: allocation.project.client.currency
+ })
+
+ const [openPayments, setOpenPayments] = useState(false)
+
+ const handleClickPayments = () => {
+ setOpenPayments(!openPayments)
+ }
+ const onClickPayment = (payment) => {
+ if (payment) {
+ setSelectedPayment(payment)
+ } else {
+ setSelectedPayment(null)
+ }
+ setOpenPayments(false)
+ }
+
+ const paymentAmount = (
+ selectedPayment
+ ? formatAmount({
+ amount: selectedPayment.amount / 100,
+ currencyInformation: currencyInformation
+ })
+ : 'Proposed'
+ )
+ const datePaid = (
+ selectedPayment
+ ? selectedPayment.date_paid
+ ? moment(selectedPayment.date_paid, 'x').format('MM/DD/YYYY')
+ : selectedPayment.date_incurred
+ ? 'Warning: This payment has not been paid'
+ : 'Proposed'
+ : ''
+ )
+
+ const listPayments = (payments) => {
+ const paymentsList = differenceWith(payments, [selectedPayment], isEqual)
+ return paymentsList.map(payment => {
+ const paymentAmount = (
+ payment
+ ? (formatAmount({
+ amount: payment.amount / 100,
+ currencyInformation: currencyInformation
+ }))
+ : null
+ )
+ return (
+
+ onClickPayment(payment)}>
+
+
+
+
+
+
+ {`${payment
+ ? payment.date_paid
+ ? moment(payment.date_paid, 'x').format('MM/DD/YYYY')
+ : 'Not paid yet'
+ : 'Proposed'
+ }`}
+
+
+
+
+
+
+ )
+ })
+ }
+
+ return (
+
+
+
+ {`Project`}
+
+
+ {`${allocation.project.name}`}
+
+
+
+
+ {`Contributor`}
+
+ {`${allocation.contributor.name}`}
+
+
+
+
+
+ {`Client`}
+
+
+ {`${allocation.project.client.name}`}
+
+
+
+
+ {`Payment`}
+
+
+
+
+
+
+
+
+
+
+ {openPayments
+ ?
+ :
+ }
+
+
+
+
+ {payments.length > 1 &&
+ listPayments(payments)
+ }
+
+
+
+ {`Date paid: ${datePaid}`}
+
+
+
+ )
+}
+
+export default EditAllocationInfo
diff --git a/src/components/EditAllocationRate.js b/src/components/EditAllocationRate.js
new file mode 100644
index 00000000..06534bc6
--- /dev/null
+++ b/src/components/EditAllocationRate.js
@@ -0,0 +1,131 @@
+import React, { useState } from 'react'
+import { useMutation, useQuery } from '@apollo/client'
+import {
+ Box,
+ Button,
+ ButtonGroup,
+ Grid,
+ Typography
+} from '@material-ui/core/'
+import moment from 'moment'
+import DatePicker from 'react-datepicker'
+import { findKey } from 'lodash'
+
+import LoadingProgress from './LoadingProgress'
+import RateProratedMonthlyForm from './RateProratedMonthlyForm'
+import RateMaxBudgetForm from './RateMaxBudgetForm'
+import {
+ GET_CONTRIBUTOR_ALLOCATIONS,
+ GET_CONTRIBUTOR_RATES
+} from '../operations/queries/ContributorQueries'
+import { CREATE_RATE } from '../operations/mutations/RateMutations'
+import { UPDATE_ALLOCATION } from '../operations/mutations/AllocationMutations'
+
+const EditAllocationRate = (props) => {
+
+ const {
+ allocation,
+ currency,
+ endDate,
+ rate,
+ selectedPayment,
+ setEndDate,
+ setNewAllocationRate,
+ setSelectedCurrency,
+ setStartDate,
+ startDate
+ } = props
+
+ const [selectedRateType, setSelectedRateType] = useState(rate.type)
+
+ // console.log('selectedPayment');
+ // console.log(selectedPayment);
+
+ const getRangedTimeEntries = (dates) => {
+ const [start, end] = dates
+ setStartDate(start)
+ setEndDate(end)
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ getRangedTimeEntries(date)}
+ customInput={
+
+ {`${
+ startDate
+ ? moment(startDate).format('MM/DD/YYYY')
+ : 'Start date'
+ } - ${
+ endDate
+ ? moment(endDate).format('MM/DD/YYYY')
+ : ' End date'
+ }`}
+
+ }
+ />
+
+
+ {selectedRateType == 'prorated_monthly'
+ ? (
+
+ ) : (
+
+ )
+ }
+
+ )
+}
+
+export default EditAllocationRate
diff --git a/src/components/EmptyState.js b/src/components/EmptyState.js
new file mode 100644
index 00000000..9808e346
--- /dev/null
+++ b/src/components/EmptyState.js
@@ -0,0 +1,30 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Grid,
+ Icon,
+ Typography
+} from '@material-ui/core'
+import GroupAddIcon from '@material-ui/icons/GroupAdd';
+
+const EmptyState = (props) => {
+ const {
+ description,
+ iconClassname
+ } = props
+
+ return (
+
+
+
+ {`${description}`}
+
+
+
+
+
+
+ )
+}
+
+export default EmptyState
diff --git a/src/components/PaymentTile.js b/src/components/PaymentTile.js
index 696ffadc..b34f4768 100644
--- a/src/components/PaymentTile.js
+++ b/src/components/PaymentTile.js
@@ -16,6 +16,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import MonetizationOnIcon from '@material-ui/icons/MonetizationOn'
import AllocationAddForm from './AllocationAddForm'
+import AllocationOverview from './AllocationOverview'
import DeletePayment from './DeletePayment'
import PaymentEditDialog from './PaymentEditDialog'
import {
@@ -26,7 +27,6 @@ import {
formatAmount,
selectCurrencyInformation
} from '../scripts/selectors'
-
import { red } from '../styles/colors.scss'
const PaymentTile = (props) => {
@@ -58,13 +58,19 @@ const PaymentTile = (props) => {
const [paymentClicked, setPaymentClicked] = useState(null)
const [openAddAllocationDialog, setOpenAddAllocationDialog] = useState(false)
+ const [openAllocationOverview, setOpenAllocationOverview] = useState(false)
const [openDeletePayment, setOpenDeletePayment] = useState(false)
const [openEditPayment, setOpenEditPayment] = useState(false)
+ const [selectedAllocation, setSelectedAllocation] = useState(null)
const addAllocation = (props) => {
setOpenAddAllocationDialog(true)
setPaymentClicked(props.payment)
}
+ const handleAllocationClicked = ({ value, allocation }) => {
+ setSelectedAllocation(allocation)
+ setOpenAllocationOverview(value)
+ }
const handleAddAllocationClose = () => {
setOpenAddAllocationDialog(false)
}
@@ -99,7 +105,7 @@ const PaymentTile = (props) => {
currencyInformation
} = props
- return allocations.map(a => {
+ return allocations.map((a, i) => {
const {
amount,
contributor,
@@ -113,7 +119,10 @@ const PaymentTile = (props) => {
return (
-
+ handleAllocationClicked({ value: true, allocation: a })}
+ >
{`${contributor.name}`}
@@ -160,9 +169,7 @@ const PaymentTile = (props) => {
- {
- `${paymentAmount}`
- }
+ {`${paymentAmount}`}
@@ -171,10 +178,9 @@ const PaymentTile = (props) => {
align='left'
color='secondary'
>
- {
- `${paymentHasBeenMade
- ? formattedDatePaid
- : formattedDateIncurred}`
+ {`${paymentHasBeenMade
+ ? formattedDatePaid
+ : formattedDateIncurred}`
}
@@ -219,7 +225,6 @@ const PaymentTile = (props) => {
>
-
@@ -264,6 +269,13 @@ const PaymentTile = (props) => {
open={openEditPayment}
onClose={() => handleEditPayment(false)}
/>
+ {selectedAllocation &&
+ handleAllocationClicked(false)}
+ open={openAllocationOverview}
+ />
+ }
{
}, [])
useEffect(() => {
- if (githubContributors.length) {
- setContributors(contributors.concat(...githubContributors))
+ try {
+ if (githubContributors.length) {
+ setContributors(contributors.concat(...githubContributors))
+ }
+ } catch (error) {
+ console.log(`error: ${error}`)
}
}, [githubContributors])
diff --git a/src/components/ProjectPayments.js b/src/components/ProjectPayments.js
index 399e191d..03d51cc6 100644
--- a/src/components/ProjectPayments.js
+++ b/src/components/ProjectPayments.js
@@ -10,7 +10,7 @@ import {
} from '@material-ui/core'
import AddIcon from '@material-ui/icons/Add'
import MonetizationOnIcon from '@material-ui/icons/MonetizationOn'
-import { filter, orderBy } from 'lodash'
+import { filter, isEmpty, orderBy } from 'lodash'
import LoadingProgress from './LoadingProgress'
import PaymentsEmptyState from './PaymentsEmptyState'
@@ -100,13 +100,13 @@ const ProjectPayments = (props) => {
project={getProjectById}
currencyInformation={currencyInformation}
/>
- {allocatedPayments.length != 0 &&
+ {!isEmpty(allocatedPayments) &&
}
- {proposedAllocations.length &&
+ {!isEmpty(proposedAllocations) &&
(
diff --git a/src/components/ProjectProposedAllocationsTile.js b/src/components/ProjectProposedAllocationsTile.js
index 6619dcf9..6519d6b3 100644
--- a/src/components/ProjectProposedAllocationsTile.js
+++ b/src/components/ProjectProposedAllocationsTile.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useQuery } from '@apollo/client'
import {
Accordion,
@@ -11,10 +11,9 @@ import {
Typography
} from '@material-ui/core'
+import AllocationOverview from './AllocationOverview'
import { GET_PROJECT_TOTAL_PROPOSED } from '../operations/queries/ProjectQueries'
-import {
- formatAmount
-} from '../scripts/selectors'
+import { formatAmount } from '../scripts/selectors'
import moment from 'moment'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
@@ -37,6 +36,14 @@ const ProjectProposedAllocationsTile = (props) => {
}
})
+ const [openAllocationOverview, setOpenAllocationOverview] = useState(null)
+ const [selectedAllocation, setSelectedAllocation] = useState(null)
+
+ const handleAllocationClicked = ({ value, allocation }) => {
+ setSelectedAllocation(allocation)
+ setOpenAllocationOverview(value)
+ }
+
const renderAllocations = ({ allocations, currencyInformation }) => {
return allocations.map(a => {
@@ -49,7 +56,10 @@ const ProjectProposedAllocationsTile = (props) => {
return (
-
+ handleAllocationClicked({ value: true, allocation: a })}
+ >
{`${a.contributor.name}`}
@@ -62,9 +72,9 @@ const ProjectProposedAllocationsTile = (props) => {
- {`${currencyInformation['symbol']}${a.rate.hourly_rate}/hr (
- ${a.rate.type == 'monthly_rate' ? 'monthly rate' : 'max budget'}
- )`}
+ {`
+ ${currencyInformation['symbol']}${a.rate.hourly_rate}/hr (${a.rate.type == 'monthly_rate' ? 'monthly rate' : 'max budget'})
+ `}
@@ -117,7 +127,6 @@ const ProjectProposedAllocationsTile = (props) => {
{`${totalAllocatedNotPaid}`}
-
@@ -129,10 +138,15 @@ const ProjectProposedAllocationsTile = (props) => {
+ {selectedAllocation &&
+ handleAllocationClicked(false)}
+ open={openAllocationOverview}
+ />
+ }
-
)
-
}
export default ProjectProposedAllocationsTile
diff --git a/src/components/RateMaxBudgetForm.js b/src/components/RateMaxBudgetForm.js
index a96d6cd4..2d9eaa33 100644
--- a/src/components/RateMaxBudgetForm.js
+++ b/src/components/RateMaxBudgetForm.js
@@ -19,8 +19,10 @@ const RateMaxBudgetForm = (props) => {
const {
clientCurrency,
currentRate,
+ currentTotal,
createRate,
endDate,
+ selectedPayment,
setCurrency,
setNewAllocationRate,
startDate
@@ -28,7 +30,7 @@ const RateMaxBudgetForm = (props) => {
const [currentRateInput, setCurrentRateInput] = useState(null)
const [rateCurrency, setRateCurrency] = useState(clientCurrency)
- const [totalAmount, setTotalAmount] = useState(0)
+ const [totalAmount, setTotalAmount] = useState(currentTotal ? currentTotal / 100 : 0)
const [totalWeeks, setTotalWeeks] = useState(null)
const [totalHours, setTotalHours] = useState(0)
@@ -37,12 +39,19 @@ const RateMaxBudgetForm = (props) => {
setCurrency(rateCurrency)
}
}, [rateCurrency])
+
useEffect(() => {
setTotalWeeks(endDate.diff(startDate, 'days') / 7)
setCurrentRateInput(currentRate ? currentRate.hourly_rate : 0)
setRateCurrency(currentRate ? currentRate.currency : clientCurrency)
}, [currentRate])
+ useEffect(() => {
+ if (selectedPayment) {
+ setRateCurrency(clientCurrency)
+ }
+ }, [selectedPayment])
+
useEffect(() => {
setTotalHours(totalAmount && currentRateInput ? ((totalAmount / 100) / currentRateInput).toFixed(2) : 0)
setNewAllocationRate({
@@ -85,7 +94,8 @@ const RateMaxBudgetForm = (props) => {
name='Currency'
fullWidth
onChange={(event) => setRateCurrency(event.target.value)}
- value={rateCurrency}
+ value={selectedPayment ? clientCurrency : rateCurrency}
+ disabled={selectedPayment}
>
{renderCurrencies(CURRENCIES)}
@@ -113,7 +123,7 @@ const RateMaxBudgetForm = (props) => {
variant='filled'
currencySymbol={`${currencyInformation['symbol']}`}
minimumValue='0'
- defautltValue='0'
+ defaultValue={currentTotal / 100}
outputFormat='string'
decimalCharacter={`${currencyInformation['decimal']}`}
digitGroupSeparator={`${currencyInformation['thousand']}`}
diff --git a/src/components/RateProratedMonthlyForm.js b/src/components/RateProratedMonthlyForm.js
index bbdc5019..ce7f5bd5 100644
--- a/src/components/RateProratedMonthlyForm.js
+++ b/src/components/RateProratedMonthlyForm.js
@@ -21,6 +21,7 @@ const RateProratedMonthlyForm = (props) => {
clientCurrency,
currentRate,
endDate,
+ selectedPayment,
setNewAllocationRate,
setCurrency,
startDate
@@ -28,17 +29,11 @@ const RateProratedMonthlyForm = (props) => {
const [monthlyHoursInput, setMonthlyhoursInput] = useState(null)
const [currentRateInput, setCurrentRateInput] = useState(null)
- const [rateCurrency, setRateCurrency] = useState(clientCurrency)
+ const [rateCurrency, setRateCurrency] = useState(null)
const [totalAmount, setTotalAmount] = useState(null)
const [totalWeeks, setTotalWeeks] = useState(null)
const [totalHours, setTotalHours] = useState(0)
- useEffect(() => {
- if (rateCurrency) {
- setCurrency(rateCurrency)
- }
- }, [rateCurrency])
-
useEffect(() => {
setTotalWeeks(endDate.diff(startDate, 'days') / 7)
setCurrentRateInput(currentRate ? currentRate.hourly_rate : 0)
@@ -46,6 +41,19 @@ const RateProratedMonthlyForm = (props) => {
setRateCurrency(currentRate ? currentRate.currency : clientCurrency)
}, [currentRate])
+ useEffect(() => {
+ if (selectedPayment) {
+ setRateCurrency(clientCurrency)
+ }
+ }, [selectedPayment])
+
+ useEffect(() => {
+ if (!rateCurrency) {
+ setRateCurrency(clientCurrency)
+ }
+ setCurrency(rateCurrency)
+ }, [rateCurrency])
+
useEffect(() => {
setTotalAmount(currentRateInput * monthlyHoursInput)
}, [monthlyHoursInput, currentRateInput])
@@ -96,7 +104,8 @@ const RateProratedMonthlyForm = (props) => {
name='Currency'
fullWidth
onChange={(event) => setRateCurrency(event.target.value)}
- value={rateCurrency}
+ value={selectedPayment ? clientCurrency : rateCurrency}
+ disabled={selectedPayment}
>
{renderCurrencies(CURRENCIES)}
diff --git a/src/constants/index.js b/src/constants/index.js
index 6790b215..8dd5f136 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -98,6 +98,10 @@ export const NAV_TITLES = [
title: 'Clients',
locations: ['/home/clients']
},
+ {
+ title: 'Contributor',
+ locations: ['/contributor']
+ },
{
title: 'Login',
locations: ['/login']
diff --git a/src/operations/mutations/AllocationMutations.js b/src/operations/mutations/AllocationMutations.js
index c6112faa..e1f4646e 100644
--- a/src/operations/mutations/AllocationMutations.js
+++ b/src/operations/mutations/AllocationMutations.js
@@ -30,3 +30,35 @@ export const CREATE_ALLOCATION = gql`
}
}
`
+
+export const DELETE_ALLOCATION = gql`
+ mutation DeleteAllocation(
+ $id: Int!
+ ) {
+ deleteAllocationById(id: $id)
+ }
+`
+
+export const UPDATE_ALLOCATION = gql`
+ mutation UpdateAllocation(
+ $id: Int!,
+ $amount: Int!,
+ $start_date: String!,
+ $end_date: String!,
+ $rate_id: Int!,
+ $payment_id: Int
+ ) {
+ updateAllocationById(
+ id: $id
+ updateFields: {
+ amount: $amount
+ start_date: $start_date
+ end_date: $end_date
+ rate_id: $rate_id
+ payment_id: $payment_id
+ }
+ ){
+ id
+ }
+ }
+`
diff --git a/src/operations/queries/AllocationQueries.js b/src/operations/queries/AllocationQueries.js
index c9a146e4..024b007a 100644
--- a/src/operations/queries/AllocationQueries.js
+++ b/src/operations/queries/AllocationQueries.js
@@ -8,6 +8,10 @@ export const GET_ALLOCATIONS = gql`
start_date
end_date
date_paid
+ contributor {
+ id
+ name
+ }
rate {
id
active
@@ -18,12 +22,59 @@ export const GET_ALLOCATIONS = gql`
}
payment {
id
+ amount
+ client {
+ id
+ name
+ currency
+ }
+ }
+ project {
+ id
+ name
+ client {
+ id
+ name
+ currency
+ }
+ }
+ }
+ }
+`
+
+export const GET_ALLOCATION_INFO = gql`
+ query Allocation($id: Int!) {
+ getAllocationById(id: $id) {
+ id
+ amount
+ start_date
+ end_date
+ payment {
+ id
+ amount
+ date_incurred
+ date_paid
+ }
+ contributor {
+ id
+ name
+ }
+ project {
+ id
+ name
client {
id
name
currency
}
}
+ rate {
+ id
+ hourly_rate
+ total_expected_hours
+ type
+ currency
+ }
}
}
`
diff --git a/src/operations/queries/ClientQueries.js b/src/operations/queries/ClientQueries.js
index 0ec85ae8..14647434 100644
--- a/src/operations/queries/ClientQueries.js
+++ b/src/operations/queries/ClientQueries.js
@@ -73,6 +73,8 @@ query ClientPayments($clientId: Int!) {
payments {
id
amount
+ date_incurred
+ date_paid
}
}
}
diff --git a/src/operations/queries/ContributorQueries.js b/src/operations/queries/ContributorQueries.js
index f1e64dcd..38950e67 100644
--- a/src/operations/queries/ContributorQueries.js
+++ b/src/operations/queries/ContributorQueries.js
@@ -35,6 +35,9 @@ export const GET_CONTRIBUTOR_ALLOCATIONS = gql`
allocations {
id
amount
+ contributor {
+ id
+ }
active
start_date
end_date
@@ -68,7 +71,11 @@ export const GET_CONTRIBUTOR_INFO = gql`
github_id
github_handle
github_access_token
- total_paid
+ totalPaid
+ paidByCurrency {
+ currency
+ amount
+ }
}
}
`
@@ -83,6 +90,7 @@ export const GET_CONTRIBUTOR_RATES = gql`
active
type
hourly_rate
+ currency
total_expected_hours
}
diff --git a/src/pages/ContributorDetailPage.js b/src/pages/ContributorDetailPage.js
index adbc609c..351ffd45 100644
--- a/src/pages/ContributorDetailPage.js
+++ b/src/pages/ContributorDetailPage.js
@@ -23,12 +23,6 @@ class ContributorDetailPage extends React.Component {
align='center'
className='ContributorDetailPage'
>
-
{
location
} = props
const locationTitle = NAV_TITLES.find(tl => {
- return tl.locations.find(l => location.match(l))
+ return tl.locations.find(l => {
+ return location.includes(l)
+ })
})
return locationTitle
}