1+ import React , { useEffect } from "react" ;
2+ import ConsolePageTitle from "../../components/ConsolePageTitle" ;
3+ import Table from '@mui/material/Table' ;
4+ import TableBody from '@mui/material/TableBody' ;
5+ import TableCell from '@mui/material/TableCell' ;
6+ import TableContainer from '@mui/material/TableContainer' ;
7+ import TableHead from '@mui/material/TableHead' ;
8+ import TableRow from '@mui/material/TableRow' ;
9+ import { Grid , Paper , Typography } from "@mui/material" ;
10+ import Button from "@mui/material/Button" ;
11+ import Box from "@mui/material/Box/Box" ;
12+ import { observer } from "mobx-react-lite" ;
13+ import { consultingStore } from "../../stores/consulting" ;
14+ import { ConsultingWrapperProps } from "./ConsultingWrapper" ;
15+ import { makeStyles } from "@material-ui/core" ;
16+ import { PageSpinner } from "@postgres.ai/shared/components/PageSpinner" ;
17+ import { ProductCardWrapper } from "../../components/ProductCard/ProductCardWrapper" ;
18+ import { Link } from "@postgres.ai/shared/components/Link2" ;
19+ import Permissions from "../../utils/permissions" ;
20+ import { WarningWrapper } from "../../components/Warning/WarningWrapper" ;
21+ import { messages } from "../../assets/messages" ;
22+ import { ConsoleBreadcrumbsWrapper } from "../../components/ConsoleBreadcrumbs/ConsoleBreadcrumbsWrapper" ;
23+ import { formatPostgresInterval } from "./utils" ;
24+ 25+ 26+ 27+ const useStyles = makeStyles ( ( theme ) => ( {
28+ sectionLabel : {
29+ fontSize : '14px!important' ,
30+ fontWeight : '700!important' as 'bold' ,
31+ } ,
32+ productCardProjects : {
33+ flex : '1 1 0' ,
34+ marginRight : '20px' ,
35+ height : 'maxContent' ,
36+ gap : 20 ,
37+ maxHeight : '100%' ,
38+ 39+ '& svg' : {
40+ width : '206px' ,
41+ height : '130px' ,
42+ } ,
43+ 44+ [ theme . breakpoints . down ( 'sm' ) ] : {
45+ flex : '100%' ,
46+ marginTop : '20px' ,
47+ minHeight : 'auto !important' ,
48+ 49+ '&:nth-child(1) svg' : {
50+ marginBottom : 0 ,
51+ } ,
52+ 53+ '&:nth-child(2) svg' : {
54+ marginBottom : 0 ,
55+ } ,
56+ } ,
57+ } ,
58+ } ) )
59+ 60+ export const Consulting = observer ( ( props : ConsultingWrapperProps ) => {
61+ const { orgId, orgData, match } = props ;
62+ 63+ const classes = useStyles ( ) ;
64+ 65+ useEffect ( ( ) => {
66+ if ( orgId ) {
67+ consultingStore . getOrgBalance ( orgId ) ;
68+ consultingStore . getTransactions ( orgId ) ;
69+ }
70+ } , [ orgId ] ) ;
71+ 72+ const breadcrumbs = (
73+ < ConsoleBreadcrumbsWrapper
74+ org = { match . params . org }
75+ breadcrumbs = { [ { name : "Consulting" } ] }
76+ />
77+ )
78+ 79+ if ( consultingStore . loading ) {
80+ return (
81+ < Box >
82+ { breadcrumbs }
83+ < ConsolePageTitle title = { "Consulting" } />
84+ < Box sx = { { display : 'flex' , justifyContent : 'center' , alignItems : 'center' , height : '100%' } } >
85+ < PageSpinner />
86+ </ Box >
87+ </ Box >
88+ )
89+ }
90+ 91+ if ( orgData === null || ! Permissions . isAdmin ( orgData ) ) {
92+ return (
93+ < Box >
94+ { breadcrumbs }
95+ < ConsolePageTitle title = { "Consulting" } />
96+ < WarningWrapper > { messages . noPermissionPage } </ WarningWrapper >
97+ </ Box >
98+ )
99+ }
100+ 101+ if ( orgData . consulting_type === null ) {
102+ return (
103+ < Box >
104+ { breadcrumbs }
105+ < ConsolePageTitle title = { "Consulting" } />
106+ < Box sx = { { display : 'flex' , justifyContent : 'center' , alignItems : 'center' , height : '100%' } } >
107+ < ProductCardWrapper
108+ inline
109+ className = { classes . productCardProjects }
110+ title = "Not a customer yet"
111+ actions = { [
112+ {
113+ id : 'learn-more' ,
114+ content : ( < Link to = "https://postgres.ai/consulting" external target = "_blank" > Learn more</ Link > )
115+ }
116+ ] }
117+ >
118+ < p >
119+ Your organization is not a consulting customer yet. To learn more about Postgres.AI consulting, visit this page: < Link to = "https://postgres.ai/consulting" external target = "_blank" > Consulting</ Link > .
120+ </ p >
121+ < p >
122+ Reach out to the team to discuss consulting opportunities: < Link to = "mailto:consulting@postgres.ai" external target = "_blank" > consulting@postgres.ai</ Link > .
123+ </ p >
124+ </ ProductCardWrapper >
125+ </ Box >
126+ </ Box >
127+ )
128+ }
129+ 130+ return (
131+ < div >
132+ { breadcrumbs }
133+ < ConsolePageTitle title = { "Consulting" } />
134+ < Grid container spacing = { 3 } >
135+ { orgData . consulting_type === 'retainer' && < Grid item xs = { 12 } md = { 8 } >
136+ < Typography variant = "h6" classes = { { root : classes . sectionLabel } } >
137+ Retainer balance:
138+ </ Typography >
139+ < Typography variant = "h5" sx = { { marginTop : 1 } } >
140+ { formatPostgresInterval ( consultingStore . orgBalance ?. [ 0 ] ?. balance || '00' ) || 0 }
141+ </ Typography >
142+ </ Grid > }
143+ < Grid item xs = { 12 } md = { 8 } >
144+ < Box >
145+ < Button variant = "contained" component = "a" href = "https://buy.stripe.com/7sI5odeXt3tB0Eg3cm" target = "_blank" >
146+ Replenish consulting hours
147+ </ Button >
148+ </ Box >
149+ </ Grid >
150+ < Grid item xs = { 12 } md = { 8 } >
151+ < Box >
152+ < Typography variant = "h6" classes = { { root : classes . sectionLabel } } >
153+ Issue tracker (GitLab):
154+ </ Typography >
155+ < Typography variant = "body1" sx = { { marginTop : 1 , fontSize : 14 } } >
156+ < Link to = { `https://gitlab.com/postgres-ai/postgresql-consulting/support/${ orgData . alias } ` } external target = "_blank" >
157+ https://gitlab.com/postgres-ai/postgresql-consulting/support/{ orgData . alias }
158+ </ Link >
159+ </ Typography >
160+ </ Box >
161+ </ Grid >
162+ < Grid item xs = { 12 } md = { 8 } >
163+ < Box >
164+ < Typography variant = "h6" classes = { { root : classes . sectionLabel } } >
165+ Book a Zoom call:
166+ </ Typography >
167+ < Typography variant = "body1" sx = { { marginTop : 1 , fontSize : 14 } } >
168+ < Link to = { `https://calend.ly/postgres` } external target = "_blank" >
169+ https://calend.ly/postgres
170+ </ Link >
171+ </ Typography >
172+ </ Box >
173+ </ Grid >
174+ < Grid item xs = { 12 } md = { 8 } >
175+ < Typography variant = "h6" classes = { { root : classes . sectionLabel } } >
176+ Activity:
177+ </ Typography >
178+ {
179+ consultingStore . transactions ?. length === 0
180+ ? < Typography variant = "body1" sx = { { marginTop : 1 } } >
181+ No activity yet
182+ </ Typography >
183+ : < TableContainer component = { Paper } sx = { { marginTop : 1 } } >
184+ < Table >
185+ < TableHead >
186+ < TableRow >
187+ < TableCell > Action</ TableCell >
188+ < TableCell > Amount</ TableCell >
189+ < TableCell > Date</ TableCell >
190+ < TableCell > Details</ TableCell >
191+ </ TableRow >
192+ </ TableHead >
193+ < TableBody >
194+ {
195+ consultingStore . transactions . map ( ( transaction , index ) => {
196+ return (
197+ < TableRow key = { index } >
198+ < TableCell sx = { { whiteSpace : 'nowrap' } } > { transaction . amount . charAt ( 0 ) === '-' ? 'Utilize' : 'Replenish' } </ TableCell >
199+ < TableCell sx = { { color : transaction . amount . charAt ( 0 ) === '-' ? 'red' : 'green' , whiteSpace : 'nowrap' } } >
200+ { formatPostgresInterval ( transaction . amount || '00' ) }
201+ </ TableCell >
202+ < TableCell sx = { { whiteSpace : 'nowrap' } } > { new Date ( transaction . created_at ) ?. toISOString ( ) ?. split ( 'T' ) ?. [ 0 ] } </ TableCell >
203+ < TableCell >
204+ { transaction . issue_id
205+ ? < Link external to = { `https://gitlab.com/postgres-ai/postgresql-consulting/support/${ orgData . alias } /-/issues/${ transaction . issue_id } ` } target = "_blank" >
206+ { transaction . description }
207+ </ Link >
208+ : transaction . description
209+ }
210+ </ TableCell >
211+ </ TableRow >
212+ ) ;
213+ } )
214+ }
215+ </ TableBody >
216+ </ Table >
217+ </ TableContainer >
218+ }
219+ </ Grid >
220+ </ Grid >
221+ </ div >
222+ ) ;
223+ } ) ;
0 commit comments