Skip to content

Commit 71b3cd6

Browse files
authored
Merge pull request #5801 from bcgov/feat/5720
feat(5720): add new history view on public cloud requests
2 parents 12caf82 + 7775666 commit 71b3cd6

File tree

15 files changed

+539
-214
lines changed

15 files changed

+539
-214
lines changed

app/app/public-cloud/products/(product)/[licencePlate]/history/loading.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

app/app/public-cloud/products/(product)/[licencePlate]/history/page.tsx

Lines changed: 0 additions & 48 deletions
This file was deleted.

app/app/public-cloud/products/(product)/[licencePlate]/layout.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import createClientPage from '@/core/client-page';
1414
import { Provider } from '@/prisma/client';
1515
import { getPublicCloudProduct } from '@/services/backend/public-cloud/products';
1616
import { usePublicProductState } from '@/states/global';
17-
import { resetState as resetRequestsState } from './requests/state';
17+
import { resetState as resetRequestsState } from './requests/ListView/state';
1818

1919
const pathParamSchema = z.object({
2020
licencePlate: z.string(),
@@ -77,14 +77,6 @@ export default publicCloudProductSecurityACS(({ getPathParams, children }) => {
7777
});
7878
}
7979

80-
if (currentProduct?._permissions.viewHistory) {
81-
tabs.push({
82-
label: 'HISTORY',
83-
name: 'history',
84-
href: `/public-cloud/products/${licencePlate}/history`,
85-
});
86-
}
87-
8880
if (!snap.currentProduct || snap.currentProduct.licencePlate !== licencePlate) {
8981
return null;
9082
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { Stepper, Timeline, Text, rem, HoverCard, Blockquote } from '@mantine/core';
2+
import {
3+
IconCircleX,
4+
IconConfetti,
5+
IconCancel,
6+
IconInfoCircle,
7+
IconChecklist,
8+
IconFileAnalytics,
9+
IconLoader,
10+
} from '@tabler/icons-react';
11+
import React from 'react';
12+
import TimeAgo from '@/components/generic/TimeAgo';
13+
import UserCard from '@/components/UserCard';
14+
import { DecisionStatus } from '@/prisma/client';
15+
import { PublicCloudRequestDetail } from '@/types/public-cloud';
16+
17+
export default function RequestTimeLine({
18+
request,
19+
preReviewSteps,
20+
className,
21+
}: {
22+
request: Pick<
23+
PublicCloudRequestDetail,
24+
| 'decisionMakerEmail'
25+
| 'decisionMaker'
26+
| 'decisionStatus'
27+
| 'decisionDate'
28+
| 'decisionComment'
29+
| 'createdByEmail'
30+
| 'createdBy'
31+
| 'createdAt'
32+
| 'cancelledAt'
33+
| 'cancelledById'
34+
| 'cancelledBy'
35+
| 'provisionedDate'
36+
| 'requestComment'
37+
>;
38+
preReviewSteps?: React.ReactNode | null;
39+
className?: string;
40+
}) {
41+
if (!request) return null;
42+
43+
const getSubmissionContent = () => (
44+
<div className="max-w-xl">
45+
<Text c="dimmed" size="sm" className="flex gap-1">
46+
<span>The request has been submitted by</span>
47+
<UserCard user={request.createdBy} classNames={{ name: 'text-sm' }} />
48+
</Text>
49+
{request.requestComment && (
50+
<Blockquote color="blue" className="mt-0 p-2 italic text-sm max-w-lg">
51+
{request.requestComment}
52+
</Blockquote>
53+
)}
54+
<TimeAgo date={request.createdAt} />
55+
</div>
56+
);
57+
58+
const getDecisionContent = (decision: DecisionStatus) => {
59+
let text = 'reviewed';
60+
if (decision === DecisionStatus.APPROVED) text = 'approved';
61+
else if (decision === DecisionStatus.REJECTED) text = 'rejected';
62+
else if (decision === DecisionStatus.AUTO_APPROVED) text = 'auto-approved';
63+
64+
return (
65+
<div className="max-w-xl">
66+
<Text c="dimmed" size="sm" className="flex gap-1">
67+
<div>The request has been {text}</div>
68+
{request.decisionMaker && (
69+
<>
70+
<span>by</span>
71+
<UserCard user={request.decisionMaker} classNames={{ name: 'text-sm' }} />
72+
</>
73+
)}
74+
</Text>
75+
{request.decisionComment && (
76+
<Blockquote color="green" className="mt-0 p-2 italic text-sm max-w-lg">
77+
{request.decisionComment}
78+
</Blockquote>
79+
)}
80+
<TimeAgo date={request.decisionDate} />
81+
</div>
82+
);
83+
};
84+
85+
const getCancellationContent = () => (
86+
<div className="max-w-xl">
87+
<Text c="dimmed" size="sm" className="flex gap-1">
88+
<div>The request has been cancelled</div>
89+
{request.cancelledBy && (
90+
<>
91+
<span>by</span>
92+
<UserCard user={request.cancelledBy} classNames={{ name: 'text-sm' }} />
93+
</>
94+
)}
95+
</Text>
96+
<TimeAgo date={request.cancelledAt} />
97+
</div>
98+
);
99+
100+
const getProvisionContent = () => (
101+
<div className="max-w-xl">
102+
<Text c="dimmed" size="sm" className="flex gap-1">
103+
<div>The request has been provisioned</div>
104+
</Text>
105+
<TimeAgo date={request.provisionedDate} />
106+
</div>
107+
);
108+
109+
const getProvisioningContent = () => (
110+
<div className="max-w-xl">
111+
<Text c="dimmed" size="sm" className="flex gap-1">
112+
<div>The request is being provisioned</div>
113+
</Text>
114+
</div>
115+
);
116+
117+
const getReviewContent = () => (
118+
<div className="max-w-xl">
119+
<Text c="dimmed" size="sm" className="flex gap-1">
120+
<div>The request is being reviewed</div>
121+
</Text>
122+
</div>
123+
);
124+
125+
switch (request.decisionStatus) {
126+
case DecisionStatus.PENDING:
127+
return (
128+
<Timeline active={0} bulletSize={20} lineWidth={2} className="w-full">
129+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
130+
{getSubmissionContent()}
131+
</Timeline.Item>
132+
{preReviewSteps}
133+
<Timeline.Item title="Review" bullet={<IconCancel size={12} />}>
134+
{getReviewContent()}
135+
</Timeline.Item>
136+
</Timeline>
137+
);
138+
139+
case DecisionStatus.APPROVED:
140+
return (
141+
<Timeline active={1} bulletSize={20} lineWidth={2} className="w-full">
142+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
143+
{getSubmissionContent()}
144+
</Timeline.Item>
145+
{preReviewSteps}
146+
<Timeline.Item title="Approve" bullet={<IconCancel size={12} />}>
147+
{getDecisionContent(DecisionStatus.APPROVED)}
148+
</Timeline.Item>
149+
<Timeline.Item title="Provision" bullet={<IconLoader size={12} />}>
150+
{getProvisioningContent()}
151+
</Timeline.Item>
152+
</Timeline>
153+
);
154+
155+
case DecisionStatus.AUTO_APPROVED:
156+
return (
157+
<Timeline active={1} bulletSize={20} lineWidth={2} className="w-full">
158+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
159+
{getSubmissionContent()}
160+
</Timeline.Item>
161+
{preReviewSteps}
162+
<Timeline.Item title="Auto-approval" bullet={<IconCancel size={12} />}>
163+
{getDecisionContent(DecisionStatus.AUTO_APPROVED)}
164+
</Timeline.Item>
165+
<Timeline.Item title="Provision" bullet={<IconLoader size={12} />}>
166+
{getProvisioningContent()}
167+
</Timeline.Item>
168+
</Timeline>
169+
);
170+
171+
case DecisionStatus.REJECTED:
172+
return (
173+
<Timeline active={3} bulletSize={20} lineWidth={2} className="w-full">
174+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
175+
{getSubmissionContent()}
176+
</Timeline.Item>
177+
{preReviewSteps}
178+
<Timeline.Item title="Rejection" bullet={<IconCancel size={12} />}>
179+
{getDecisionContent(DecisionStatus.REJECTED)}
180+
</Timeline.Item>
181+
</Timeline>
182+
);
183+
184+
case DecisionStatus.CANCELLED:
185+
return (
186+
<Timeline active={3} bulletSize={20} lineWidth={2} className="w-full">
187+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
188+
{getSubmissionContent()}
189+
</Timeline.Item>
190+
{preReviewSteps}
191+
<Timeline.Item title="Cancellation" bullet={<IconCancel size={12} />}>
192+
{getCancellationContent()}
193+
</Timeline.Item>
194+
</Timeline>
195+
);
196+
197+
case DecisionStatus.PROVISIONED:
198+
return (
199+
<Timeline active={3} bulletSize={20} lineWidth={2} className="w-full">
200+
<Timeline.Item title="Submission" bullet={<IconFileAnalytics size={12} />}>
201+
{getSubmissionContent()}
202+
</Timeline.Item>
203+
{preReviewSteps}
204+
<Timeline.Item title="Review" bullet={<IconChecklist size={12} />}>
205+
{getDecisionContent(request.decisionMakerEmail ? DecisionStatus.APPROVED : DecisionStatus.AUTO_APPROVED)}
206+
</Timeline.Item>
207+
<Timeline.Item title="Completion" bullet={<IconConfetti size={12} />}>
208+
{getProvisionContent()}
209+
</Timeline.Item>
210+
</Timeline>
211+
);
212+
}
213+
214+
return null;
215+
}

0 commit comments

Comments
 (0)