@ -355,83 +355,139 @@
< / div >
< / div >
< / div >
< / div >
< h3 class = "text-lg font-bold mb-3 m t-4" > Given To This Idea < / h3 >
< h3 class = "text-lg font-bold mt-4" > Given To This Idea < / h3 >
< div v-if ="givesToThis.length === 0" >
< div v-if ="givesToThis.length === 0" class="text-sm" >
( None yet . If you ' ve seen something , say something by clicking a
( None yet . If you ' ve seen something , say something by clicking a
contact above . )
contact above . )
< / div >
< / div >
< ul v -else class = "text-sm border-t border-slate-300" >
< div v -else class = "mt-1 text-sm" >
< li
<!-- Totals section -- >
v - for = "give in givesToThis"
< div class = "mt-1 flex items-center min-h-[1.5rem]" >
: key = "give.jwtId"
< div v-if ="loadingTotals" class="flex-1" >
class = "py-1.5 border-b border-slate-300"
< fa icon = "spinner" class = "fa-spin-pulse text-blue-500" / >
>
< div class = "flex justify-between gap-4" >
< span >
< font -awesome icon = "user" class = "fa-fw text-slate-400" / >
{ {
serverUtil . didInfo (
give . agentDid ,
activeDid ,
allMyDids ,
allContacts ,
)
} }
< / span >
< span v-if ="give.amount" class="whitespace-nowrap" >
< font -awesome
: icon = "libsUtil.iconForUnitCode(give.unit)"
class = "fa-fw text-slate-400"
/ > { { g i v e . a m o u n t } }
< / span >
< / div >
< / div >
< div class = "text-slate-500" >
< div v -else -if = " givesTotalsByUnit.length > 0 " class=" flex - 1 " >
< font -awesome icon = "calendar" class = "fa-fw text-slate-400" / >
< span class = "font-semibold mr-2 shrink-0" > Totals < / span >
{ { give . issuedAt ? . substring ( 0 , 10 ) } }
< span class = "whitespace-nowrap overflow-hidden text-ellipsis" >
< a
@ click = "totalsExpanded = !totalsExpanded"
class = "cursor-pointer text-blue-500"
>
<!-- just show the hours , or alternatively whatever is first -- >
< span v-if ="givenTotalHours() > 0" >
{ { givenTotalHours ( ) } } { { libsUtil . UNIT_SHORT [ "HUR" ] } }
< / span >
< span v-else >
{ { givesTotalsByUnit [ 0 ] . amount } }
{ { libsUtil . UNIT_SHORT [ givesTotalsByUnit [ 0 ] . unit ] } }
< / span >
< span v-if ="givesTotalsByUnit.length > 1" > ... < / span >
< span >
< fa
: icon = "totalsExpanded ? 'chevron-up' : 'chevron-right'"
class = "fa-fw text-xs ml-1"
/ >
< / span >
< / a >
<!-- show the full list when expanded -- >
< div v-if ="totalsExpanded" >
< div
v - for = "total in givesTotalsByUnit"
: key = "total.unit"
class = "ml-2"
>
< fa
: icon = "libsUtil.iconForUnitCode(total.unit)"
class = "fa-fw text-slate-400 mr-1"
/ >
{ { total . amount } } { { libsUtil . UNIT_LONG [ total . unit ] } }
< / div >
< / div >
< / span >
< / div >
< / div >
< div v-if ="give.description" class="text-slate-500" >
< div v-else >
< font -awesome icon = "comment" class = "fa-fw text-slate-400" / >
< span class = "font-semibold mr-2 shrink-0" >
{ { give . description } }
{ { givesToThis . length } } { { givesHitLimit ? "+" : "" } } record { {
givesToThis . length === 1 ? "" : "s"
} }
< / span >
< / div >
< / div >
< div class = "flex justify-between" >
< / div >
< a @click ="onClickLoadClaim(give.jwtId)" >
< font -awesome
icon = "file-lines"
class = "text-blue-500 cursor-pointer"
/ >
< / a >
< a
<!-- List of gives -- >
v - if = "
< ul class = "mt-2 text-sm border-t border-slate-300" >
checkIsConfirmable ( give ) &&
< li
! recentlyCheckedAndUnconfirmableJwts . includes ( give . jwtId )
v - for = "give in givesToThis"
"
: key = "give.id"
@ click = "deepCheckConfirmable(give)"
class = "py-1.5 border-b border-slate-300"
>
>
< font -awesome
< div class = "flex justify-between gap-4" >
icon = "circle-check"
< span >
class = "text-blue-500 cursor-pointer"
< fa icon = "user" class = "fa-fw text-slate-400" / >
/ >
{ {
< / a >
serverUtil . didInfo (
< a v -else -if = " checkingConfirmationForJwtId = = = give.jwtId " >
give . agentDid ,
< font -awesome icon = "spinner" class = "fa-spin-pulse" / >
activeDid ,
< / a >
allMyDids ,
< a v -else @click ="shallowNotifyWhyCannotConfirm(give)" >
allContacts ,
< font -awesome
)
icon = "circle-check"
} }
class = "text-slate-500 cursor-pointer"
< / span >
/ >
< span v-if ="give.amount" class="whitespace-nowrap" >
< / a >
< fa
< / div >
: icon = "libsUtil.iconForUnitCode(give.unit)"
< div v-if ="give.fullClaim.image" class="flex justify-center" >
class = "fa-fw text-slate-400"
< a :href ="give.fullClaim.image" target = "_blank" >
/ > { { g i v e . a m o u n t } }
< img :src ="give.fullClaim.image" class = "h-24 mt-2 rounded-xl" / >
< / span >
< / a >
< / div >
< / div >
< div class = "text-slate-500" >
< / li >
< fa icon = "calendar" class = "fa-fw text-slate-400" / >
< / ul >
{ { give . issuedAt ? . substring ( 0 , 10 ) } }
< / div >
< div v-if ="give.description" class="text-slate-500" >
< fa icon = "comment" class = "fa-fw text-slate-400" / >
{ { give . description } }
< / div >
< div class = "flex justify-between" >
< a @click ="onClickLoadClaim(give.jwtId)" >
< fa icon = "file-lines" class = "text-blue-500 cursor-pointer" / >
< / a >
< a
v - if = "
checkIsConfirmable ( give ) &&
! recentlyCheckedAndUnconfirmableJwts . includes ( give . jwtId )
"
@ click = "deepCheckConfirmable(give)"
>
< fa
icon = "circle-check"
class = "text-blue-500 cursor-pointer"
/ >
< / a >
< a v -else -if = " checkingConfirmationForJwtId = = = give.jwtId " >
< fa icon = "spinner" class = "fa-spin-pulse" / >
< / a >
< a v -else @click ="shallowNotifyWhyCannotConfirm(give)" >
< fa
icon = "circle-check"
class = "text-slate-500 cursor-pointer"
/ >
< / a >
< / div >
< div v-if ="give.fullClaim.image" class="flex justify-center" >
< a :href ="give.fullClaim.image" target = "_blank" >
< img
: src = "give.fullClaim.image"
class = "h-24 mt-2 rounded-xl"
/ >
< / a >
< / div >
< / li >
< / ul >
< / div >
< div v-if ="givesHitLimit" class="text-center text-blue-500" >
< div v-if ="givesHitLimit" class="text-center text-blue-500" >
< button @click ="loadGives()" > Load More < / button >
< button @click ="loadGives()" > Load More < / button >
< / div >
< / div >
@ -646,7 +702,11 @@ export default class ProjectViewView extends Vue {
fulfillersToThis : Array < PlanSummaryRecord > = [ ] ;
fulfillersToThis : Array < PlanSummaryRecord > = [ ] ;
/** Flag for fulfiller pagination */
/** Flag for fulfiller pagination */
fulfillersToHitLimit = false ;
fulfillersToHitLimit = false ;
/** Project image URL */
givesToThis : Array < GiveSummaryRecord > = [ ] ;
givesHitLimit = false ;
givesProvidedByThis : Array < GiveSummaryRecord > = [ ] ;
givesProvidedByHitLimit = false ;
givesTotalsByUnit : Array < { unit : string ; amount : number } > = [ ] ;
imageUrl = "" ;
imageUrl = "" ;
/** Project issuer DID */
/** Project issuer DID */
issuer = "" ;
issuer = "" ;
@ -660,6 +720,7 @@ export default class ProjectViewView extends Vue {
issuerVisibleToDids : Array < string > = [ ] ;
issuerVisibleToDids : Array < string > = [ ] ;
/** Project location data */
/** Project location data */
latitude = 0 ;
latitude = 0 ;
loadingTotals = false ;
longitude = 0 ;
longitude = 0 ;
/** Project name */
/** Project name */
name = "" ;
name = "" ;
@ -689,6 +750,8 @@ export default class ProjectViewView extends Vue {
checkingConfirmationForJwtId = "" ;
checkingConfirmationForJwtId = "" ;
/** Recently checked unconfirmable JWTs */
/** Recently checked unconfirmable JWTs */
recentlyCheckedAndUnconfirmableJwts : string [ ] = [ ] ;
recentlyCheckedAndUnconfirmableJwts : string [ ] = [ ] ;
startTime = "" ;
totalsExpanded = false ;
truncatedDesc = "" ;
truncatedDesc = "" ;
/** Truncation length */
/** Truncation length */
truncateLength = 40 ;
truncateLength = 40 ;
@ -740,21 +803,26 @@ export default class ProjectViewView extends Vue {
this . projectId = decodeURIComponent ( pathParam ) ;
this . projectId = decodeURIComponent ( pathParam ) ;
}
}
this . loadProject ( this . projectId , this . activeDid ) ;
this . loadProject ( this . projectId , this . activeDid ) ;
this . loadTotals ( ) ;
}
onEditClick ( ) {
const route = {
name : "new-edit-project" ,
query : { projectId : this . projectId } ,
} ;
( this . $router as Router ) . push ( route ) ;
}
/ / I s n ' t t h e r e a b e t t e r w a y t o m a k e t h i s a v a i l a b l e t o t h e t e m p l a t e ?
expandText ( ) {
this . expanded = true ;
}
collapseText ( ) {
this . expanded = false ;
}
}
/ * *
* Loads project data and related information
*
* Workflow :
* 1. Fetches project details from API
* 2. Updates component state with project data
* 3. Initializes related data loading ( gifts , offers , fulfillments )
*
* @ param projectId Project handle ID
* @ param userDid Active user ' s DID
* @ throws Logs errors and notifies user
* @ emits Notification on loading errors
* /
async loadProject ( projectId : string , userDid : string ) {
async loadProject ( projectId : string , userDid : string ) {
this . projectId = projectId ;
this . projectId = projectId ;
@ -1394,5 +1462,56 @@ export default class ProjectViewView extends Vue {
this . allMyDids ,
this . allMyDids ,
) ;
) ;
}
}
async loadTotals ( ) {
this . loadingTotals = true ;
const url =
this . apiServer +
"/api/v2/report/givesToPlans?planIds=" +
encodeURIComponent ( JSON . stringify ( [ this . projectId ] ) ) ;
const headers = await serverUtil . getHeaders ( this . activeDid ) ;
try {
const resp = await this . axios . get ( url , { headers } ) ;
if ( resp . status === 200 && resp . data . data ) {
/ / C a l c u l a t e t o t a l s b y u n i t
const totals : { [ key : string ] : number } = { } ;
resp . data . data . forEach ( ( give : GiveSummaryRecord ) => {
const amount = give . fullClaim . object ? . amountOfThisGood ;
const unit = give . fullClaim . object ? . unitCode ;
if ( amount && unit ) {
totals [ unit ] = ( totals [ unit ] || 0 ) + amount ;
}
} ) ;
/ / C o n v e r t t o t a l s o b j e c t t o a r r a y f o r m a t
this . givesTotalsByUnit = Object . entries ( totals ) . map (
( [ unit , amount ] ) => ( {
unit ,
amount ,
} ) ,
) ;
}
} catch ( error ) {
console . error ( "Error loading totals:" , error ) ;
this . $notify (
{
group : "alert" ,
type : "danger" ,
title : "Error" ,
text : "Failed to load totals for this project." ,
} ,
5000 ,
) ;
} finally {
this . loadingTotals = false ;
}
}
givenTotalHours ( ) : number {
return (
this . givesTotalsByUnit . find ( ( total ) => total . unit === "HUR" ) ? . amount || 0
) ;
}
}
}
< / script >
< / script >