datastake-daf 0.6.121 → 0.6.123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.css +1 -1
- package/dist/components/index.js +141 -345
- package/package.json +1 -1
- package/src/@daf/core/components/Dashboard/PdfView/index.jsx +6 -12
- package/src/@daf/core/components/Dashboard/Widget/_style.scss +0 -36
- package/src/@daf/core/components/PdfForm/components/dataLinkGroupHandler.js +1 -1
- package/src/@daf/core/components/PdfForm/components/dataLinkHandler.js +2 -2
- package/src/@daf/core/components/PdfForm/index.js +34 -112
- package/src/@daf/core/components/PdfForm/storyConfig.js +2 -2
- package/src/@daf/core/components/PdfForm/style.scss +0 -9
- package/src/@daf/core/components/ProgressTabs/ProgressTabs.stories.js +118 -0
- package/src/@daf/core/components/ProgressTabs/_index.scss +100 -0
- package/src/@daf/core/components/ProgressTabs/demo.jsx +167 -0
- package/src/@daf/core/components/ProgressTabs/index.jsx +69 -0
- package/src/@daf/core/components/_style.scss +1 -0
- package/src/index.js +1 -1
- package/src/@daf/core/components/Dashboard/Widget/WidgetCatalogue/WidgetCatalogue.stories.jsx +0 -109
- package/src/@daf/core/components/Dashboard/Widget/WidgetCatalogue/index.jsx +0 -197
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, Space } from 'antd';
|
|
3
|
+
import { CheckOutlined, ScanOutlined, FileTextOutlined, CheckCircleOutlined, LockOutlined } from '@ant-design/icons';
|
|
4
|
+
import ProgressTabs from './index';
|
|
5
|
+
|
|
6
|
+
const ProgressTabsDemo = () => {
|
|
7
|
+
const [value, setValue] = useState('setup');
|
|
8
|
+
|
|
9
|
+
const options = [
|
|
10
|
+
{ label: 'Set Up', value: 'setup' },
|
|
11
|
+
{ label: 'Project Scan', value: 'scan' },
|
|
12
|
+
{ label: 'Review & Submission', value: 'review' },
|
|
13
|
+
{ label: 'Submission Status', value: 'status' }
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const currentOption = options.find(option => option.value === value);
|
|
17
|
+
const currentIndex = options.findIndex(option => option.value === value);
|
|
18
|
+
|
|
19
|
+
const handleNext = () => {
|
|
20
|
+
const nextIndex = Math.min(options.length - 1, currentIndex + 1);
|
|
21
|
+
setValue(options[nextIndex].value);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handlePrevious = () => {
|
|
25
|
+
const prevIndex = Math.max(0, currentIndex - 1);
|
|
26
|
+
setValue(options[prevIndex].value);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleValueChange = (newValue) => {
|
|
30
|
+
setValue(newValue);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
|
|
35
|
+
<h2>ProgressTabs Component Demo</h2>
|
|
36
|
+
<p>This component creates a horizontal progress bar with clickable segments, similar to the design you provided. It uses Ant Design's Segmented component as the base.</p>
|
|
37
|
+
|
|
38
|
+
<div style={{ marginBottom: '20px' }}>
|
|
39
|
+
<ProgressTabs
|
|
40
|
+
value={value}
|
|
41
|
+
options={options}
|
|
42
|
+
onChange={handleValueChange}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
|
|
47
|
+
<p><strong>Current Step:</strong> {currentOption?.label}</p>
|
|
48
|
+
<p><strong>Step Value:</strong> {value}</p>
|
|
49
|
+
<p><strong>Step Index:</strong> {currentIndex}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<Space>
|
|
53
|
+
<Button
|
|
54
|
+
onClick={handlePrevious}
|
|
55
|
+
disabled={value === 'setup'}
|
|
56
|
+
>
|
|
57
|
+
Previous
|
|
58
|
+
</Button>
|
|
59
|
+
<Button
|
|
60
|
+
type="primary"
|
|
61
|
+
onClick={handleNext}
|
|
62
|
+
disabled={value === 'status'}
|
|
63
|
+
>
|
|
64
|
+
Next
|
|
65
|
+
</Button>
|
|
66
|
+
</Space>
|
|
67
|
+
|
|
68
|
+
<div style={{ marginTop: '30px' }}>
|
|
69
|
+
<h3>Usage Example:</h3>
|
|
70
|
+
<pre style={{
|
|
71
|
+
background: '#f5f5f5',
|
|
72
|
+
padding: '15px',
|
|
73
|
+
borderRadius: '4px',
|
|
74
|
+
overflow: 'auto'
|
|
75
|
+
}}>
|
|
76
|
+
{`import { ProgressTabs } from 'datastake-daf/dist/components';
|
|
77
|
+
import { CheckOutlined, ScanOutlined, FileTextOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
|
78
|
+
|
|
79
|
+
const MyComponent = () => {
|
|
80
|
+
const [value, setValue] = useState('setup');
|
|
81
|
+
|
|
82
|
+
const options = [
|
|
83
|
+
{ label: 'Set Up', value: 'setup', icon: <CheckOutlined /> },
|
|
84
|
+
{ label: 'Project Scan', value: 'scan', icon: <ScanOutlined />, isDisabled: true },
|
|
85
|
+
{ label: 'Review & Submission', value: 'review', icon: <FileTextOutlined />, isDisabled: true },
|
|
86
|
+
{ label: 'Submission Status', value: 'status', icon: <CheckCircleOutlined />, isDisabled: true }
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ProgressTabs
|
|
91
|
+
value={value}
|
|
92
|
+
options={options}
|
|
93
|
+
onChange={setValue}
|
|
94
|
+
width="100%" // Default is 100%, can be customized
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
};`}
|
|
98
|
+
</pre>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div style={{ marginTop: '20px' }}>
|
|
102
|
+
<h3>Advanced Examples:</h3>
|
|
103
|
+
|
|
104
|
+
<div style={{ marginBottom: '20px' }}>
|
|
105
|
+
<h4>With Icons:</h4>
|
|
106
|
+
<ProgressTabs
|
|
107
|
+
value="setup"
|
|
108
|
+
options={[
|
|
109
|
+
{ label: 'Set Up', value: 'setup', icon: <CheckOutlined /> },
|
|
110
|
+
{ label: 'Project Scan', value: 'scan', icon: <ScanOutlined /> },
|
|
111
|
+
{ label: 'Review & Submission', value: 'review', icon: <FileTextOutlined /> },
|
|
112
|
+
{ label: 'Submission Status', value: 'status', icon: <CheckCircleOutlined /> }
|
|
113
|
+
]}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div style={{ marginBottom: '20px' }}>
|
|
118
|
+
<h4>With Disabled States:</h4>
|
|
119
|
+
<ProgressTabs
|
|
120
|
+
value="setup"
|
|
121
|
+
options={[
|
|
122
|
+
{ label: 'Set Up', value: 'setup' },
|
|
123
|
+
{ label: 'Project Scan', value: 'scan', isDisabled: true },
|
|
124
|
+
{ label: 'Review & Submission', value: 'review', isDisabled: true },
|
|
125
|
+
{ label: 'Submission Status', value: 'status', isDisabled: true }
|
|
126
|
+
]}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div style={{ marginBottom: '20px' }}>
|
|
131
|
+
<h4>With Icons and Disabled States:</h4>
|
|
132
|
+
<ProgressTabs
|
|
133
|
+
value="setup"
|
|
134
|
+
options={[
|
|
135
|
+
{ label: 'Set Up', value: 'setup', icon: <CheckOutlined /> },
|
|
136
|
+
{ label: 'Project Scan', value: 'scan', icon: <ScanOutlined />, isDisabled: true },
|
|
137
|
+
{ label: 'Review & Submission', value: 'review', icon: <FileTextOutlined />, isDisabled: true },
|
|
138
|
+
{ label: 'Submission Status', value: 'status', icon: <CheckCircleOutlined />, isDisabled: true }
|
|
139
|
+
]}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div style={{ marginBottom: '20px' }}>
|
|
144
|
+
<h4>Custom Width (400px):</h4>
|
|
145
|
+
<ProgressTabs
|
|
146
|
+
value="scan"
|
|
147
|
+
options={options}
|
|
148
|
+
width="400px"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div style={{ marginBottom: '20px' }}>
|
|
153
|
+
<h4>Small Width (300px):</h4>
|
|
154
|
+
<ProgressTabs
|
|
155
|
+
value="review"
|
|
156
|
+
options={options}
|
|
157
|
+
width="300px"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export default ProgressTabsDemo;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Segmented } from 'antd';
|
|
4
|
+
import './_index.scss';
|
|
5
|
+
|
|
6
|
+
const ProgressTabs = ({
|
|
7
|
+
value = 'setup',
|
|
8
|
+
options = [
|
|
9
|
+
{ label: 'Set Up', value: 'setup' },
|
|
10
|
+
{ label: 'Project Scan', value: 'scan' },
|
|
11
|
+
{ label: 'Review & Submission', value: 'review' },
|
|
12
|
+
{ label: 'Submission Status', value: 'status' }
|
|
13
|
+
],
|
|
14
|
+
onChange = () => {},
|
|
15
|
+
className = '',
|
|
16
|
+
width = '100%',
|
|
17
|
+
...rest
|
|
18
|
+
}) => {
|
|
19
|
+
// Transform options to include icons and handle disabled state
|
|
20
|
+
const transformedOptions = options.map(option => ({
|
|
21
|
+
...option,
|
|
22
|
+
label: (
|
|
23
|
+
<div className="progress-tabs__option-content">
|
|
24
|
+
|
|
25
|
+
<span className="progress-tabs__label">
|
|
26
|
+
{option.label}
|
|
27
|
+
</span>
|
|
28
|
+
{option.icon && (
|
|
29
|
+
<span className="progress-tabs__icon">
|
|
30
|
+
{option.icon}
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
disabled: option.isDisabled || false,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
className={`progress-tabs ${className}`}
|
|
41
|
+
style={{ width }}
|
|
42
|
+
>
|
|
43
|
+
<Segmented
|
|
44
|
+
value={value}
|
|
45
|
+
options={transformedOptions}
|
|
46
|
+
onChange={onChange}
|
|
47
|
+
className="progress-tabs__segmented"
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
ProgressTabs.propTypes = {
|
|
55
|
+
value: PropTypes.string,
|
|
56
|
+
options: PropTypes.arrayOf(
|
|
57
|
+
PropTypes.shape({
|
|
58
|
+
label: PropTypes.string.isRequired,
|
|
59
|
+
value: PropTypes.string.isRequired,
|
|
60
|
+
isDisabled: PropTypes.bool,
|
|
61
|
+
icon: PropTypes.node,
|
|
62
|
+
})
|
|
63
|
+
),
|
|
64
|
+
onChange: PropTypes.func,
|
|
65
|
+
className: PropTypes.string,
|
|
66
|
+
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default ProgressTabs;
|
package/src/index.js
CHANGED
|
@@ -69,7 +69,6 @@ export { default as DafDashboardDetails } from "./@daf/core/components/Dashboard
|
|
|
69
69
|
export { default as KeyIndicatorsDetails } from "./@daf/core/components/Dashboard/Widget/Details/KeyIndicatorsDetails.jsx";
|
|
70
70
|
export { default as DetailsSection } from "./@daf/core/components/Dashboard/Widget/DetailsSection/index.jsx";
|
|
71
71
|
export { default as ComponentWithFocus } from "./@daf/core/components/Dashboard/ComponentWithFocus/index.jsx";
|
|
72
|
-
export { default as WidgetCatalogue } from "./@daf/core/components/Dashboard/Widget/WidgetCatalogue/index.jsx";
|
|
73
72
|
|
|
74
73
|
// Forms
|
|
75
74
|
export { default as ViewForm } from "./@daf/core/components/ViewForm/content.jsx";
|
|
@@ -85,6 +84,7 @@ export { AjaxSelectMain as AjaxSelect } from "./@daf/core/components/EditForm/co
|
|
|
85
84
|
export { default as ProgressBar } from "./@daf/core/components/ProgressBar/index.jsx";
|
|
86
85
|
export { default as MultiBarProgress } from "./@daf/core/components/ProgressBar/MultiBarProgress/index.jsx";
|
|
87
86
|
export { default as ProgressBarSideIcon } from "./@daf/core/components/ProgressBar/components/SideIcon/index.jsx";
|
|
87
|
+
export { default as ProgressTabs } from "./@daf/core/components/ProgressTabs/index.jsx";
|
|
88
88
|
|
|
89
89
|
// Data Store
|
|
90
90
|
export { default as DataStore } from "./@daf/core/components/DataStore/index.js";
|
package/src/@daf/core/components/Dashboard/Widget/WidgetCatalogue/WidgetCatalogue.stories.jsx
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import WidgetCatalogue from "./index.jsx";
|
|
3
|
-
|
|
4
|
-
// Sample projects data matching the image exactly
|
|
5
|
-
const sampleProjects = [
|
|
6
|
-
{
|
|
7
|
-
title: "ABC Mangrove Senegal",
|
|
8
|
-
image:
|
|
9
|
-
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=200&fit=crop",
|
|
10
|
-
country: "Senegal",
|
|
11
|
-
countryFlag: "https://flagcdn.com/w40/sn.png",
|
|
12
|
-
sectoralScope: "AFOLU",
|
|
13
|
-
methodology: "VM0033",
|
|
14
|
-
imgAlt: "Mangrove landscape with calm water reflecting green trees",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
title: "IFM Jujuy",
|
|
18
|
-
image:
|
|
19
|
-
"https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=400&h=200&fit=crop",
|
|
20
|
-
country: "Argentina",
|
|
21
|
-
countryFlag: "https://flagcdn.com/w40/ar.png",
|
|
22
|
-
sectoralScope: "AFOLU",
|
|
23
|
-
methodology: "VM0033",
|
|
24
|
-
imgAlt: "White-faced whistling ducks in shallow water with green grass",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
title: "Sur del Meta",
|
|
28
|
-
image:
|
|
29
|
-
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=200&fit=crop",
|
|
30
|
-
country: "Colombia",
|
|
31
|
-
countryFlag: "https://flagcdn.com/w40/co.png",
|
|
32
|
-
sectoralScope: "AFOLU",
|
|
33
|
-
methodology: "VM0033",
|
|
34
|
-
imgAlt: "Aerial view of braided river channels through green landscape",
|
|
35
|
-
},
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
export default {
|
|
39
|
-
title: "Dashboard/Widgets/WidgetCatalogue",
|
|
40
|
-
component: WidgetCatalogue,
|
|
41
|
-
parameters: {
|
|
42
|
-
layout: "padded",
|
|
43
|
-
docs: {
|
|
44
|
-
description: {
|
|
45
|
-
component:
|
|
46
|
-
"A widget component that displays multiple project cards in a responsive grid layout, mapping over an array of project data. Each card shows project details including image, title, country, sectoral scope, methodology, and SDGs.",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
argTypes: {
|
|
51
|
-
title: {
|
|
52
|
-
control: "text",
|
|
53
|
-
description: "Widget title displayed at the top",
|
|
54
|
-
},
|
|
55
|
-
projects: {
|
|
56
|
-
control: "object",
|
|
57
|
-
description: "Array of project objects to display",
|
|
58
|
-
},
|
|
59
|
-
loading: {
|
|
60
|
-
control: "boolean",
|
|
61
|
-
description: "Loading state - shows spinner when true",
|
|
62
|
-
},
|
|
63
|
-
className: {
|
|
64
|
-
control: "text",
|
|
65
|
-
description: "Additional CSS classes for styling",
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Template for the story
|
|
71
|
-
const Template = (args) => (
|
|
72
|
-
<div
|
|
73
|
-
style={{ padding: "20px", backgroundColor: "#f5f5f5", minHeight: "100vh" }}
|
|
74
|
-
>
|
|
75
|
-
<WidgetCatalogue {...args} />
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// Populated state - matches the image exactly
|
|
80
|
-
export const Populated = Template.bind({});
|
|
81
|
-
Populated.args = {
|
|
82
|
-
title: "Project Catalogue",
|
|
83
|
-
projects: sampleProjects,
|
|
84
|
-
loading: false,
|
|
85
|
-
};
|
|
86
|
-
Populated.parameters = {
|
|
87
|
-
docs: {
|
|
88
|
-
description: {
|
|
89
|
-
story:
|
|
90
|
-
"Widget with multiple project cards, matching the design from the reference image. Shows three projects with images, country flags, and SDG icons.",
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Single project
|
|
96
|
-
export const SingleProject = Template.bind({});
|
|
97
|
-
SingleProject.args = {
|
|
98
|
-
title: "Featured Project",
|
|
99
|
-
projects: [sampleProjects[0]],
|
|
100
|
-
loading: false,
|
|
101
|
-
};
|
|
102
|
-
SingleProject.parameters = {
|
|
103
|
-
docs: {
|
|
104
|
-
description: {
|
|
105
|
-
story:
|
|
106
|
-
"Widget with a single project card - shows how the grid adapts to fewer items.",
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
};
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Image, Empty, Spin } from "antd";
|
|
3
|
-
import { ExpandOutlined } from "@ant-design/icons";
|
|
4
|
-
import { Card } from "antd";
|
|
5
|
-
import Widget from "../index.jsx";
|
|
6
|
-
|
|
7
|
-
// SDG configuration with proper colors and icons
|
|
8
|
-
const SDG_CONFIG = {
|
|
9
|
-
1: {
|
|
10
|
-
name: "No Poverty",
|
|
11
|
-
color: "#E5243B",
|
|
12
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-01.jpg",
|
|
13
|
-
},
|
|
14
|
-
2: {
|
|
15
|
-
name: "Zero Hunger",
|
|
16
|
-
color: "#DDA63A",
|
|
17
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-02.jpg",
|
|
18
|
-
},
|
|
19
|
-
3: {
|
|
20
|
-
name: "Good Health and Well-being",
|
|
21
|
-
color: "#4C9F38",
|
|
22
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-03.jpg",
|
|
23
|
-
},
|
|
24
|
-
4: {
|
|
25
|
-
name: "Quality Education",
|
|
26
|
-
color: "#C5192D",
|
|
27
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-04.jpg",
|
|
28
|
-
},
|
|
29
|
-
6: {
|
|
30
|
-
name: "Clean Water and Sanitation",
|
|
31
|
-
color: "#26BDE2",
|
|
32
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-06.jpg",
|
|
33
|
-
},
|
|
34
|
-
8: {
|
|
35
|
-
name: "Decent Work and Economic Growth",
|
|
36
|
-
color: "#A21942",
|
|
37
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-08.jpg",
|
|
38
|
-
},
|
|
39
|
-
13: {
|
|
40
|
-
name: "Climate Action",
|
|
41
|
-
color: "#48773E",
|
|
42
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-13.jpg",
|
|
43
|
-
},
|
|
44
|
-
15: {
|
|
45
|
-
name: "Life on Land",
|
|
46
|
-
color: "#3EB049",
|
|
47
|
-
icon: "https://sdgs.un.org/sites/default/files/goals/E_SDG_Icons-15.jpg",
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Default SDGs to show (matching the image)
|
|
52
|
-
const DEFAULT_SDGS = [1, 2, 3, 4, 6, 8, 13, 15];
|
|
53
|
-
|
|
54
|
-
// Individual Project Card Component
|
|
55
|
-
const ProjectCard = ({
|
|
56
|
-
title,
|
|
57
|
-
image,
|
|
58
|
-
imgAlt,
|
|
59
|
-
country,
|
|
60
|
-
countryFlag,
|
|
61
|
-
sectoralScope,
|
|
62
|
-
methodology,
|
|
63
|
-
sdgs = [],
|
|
64
|
-
}) => {
|
|
65
|
-
return (
|
|
66
|
-
<Card
|
|
67
|
-
style={{
|
|
68
|
-
minWidth: 280,
|
|
69
|
-
width: "100%",
|
|
70
|
-
borderRadius: "12px",
|
|
71
|
-
overflow: "hidden",
|
|
72
|
-
border: "1px solid #e5e7eb",
|
|
73
|
-
boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
|
|
74
|
-
}}
|
|
75
|
-
cover={
|
|
76
|
-
<div className="relative w-full h-40 overflow-hidden">
|
|
77
|
-
{image ? (
|
|
78
|
-
<img
|
|
79
|
-
alt={imgAlt || `${title} image`}
|
|
80
|
-
src={image}
|
|
81
|
-
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
82
|
-
/>
|
|
83
|
-
) : (
|
|
84
|
-
<div className="w-full h-full flex items-center justify-center bg-gray-100">
|
|
85
|
-
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
86
|
-
</div>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
}
|
|
90
|
-
bodyStyle={{ padding: "16px" }}
|
|
91
|
-
>
|
|
92
|
-
{/* Title */}
|
|
93
|
-
{title && (
|
|
94
|
-
<h3 className="text-md font-semibold text-gray-900 mb-3">{title}</h3>
|
|
95
|
-
)}
|
|
96
|
-
|
|
97
|
-
{/* Country */}
|
|
98
|
-
{country && (
|
|
99
|
-
<div className="flex items-center justify-between py-2 border-b border-gray-200">
|
|
100
|
-
<span className="text-sm text-gray-600">Country</span>
|
|
101
|
-
<div className="flex items-center gap-1">
|
|
102
|
-
{countryFlag && (
|
|
103
|
-
<img
|
|
104
|
-
src={countryFlag}
|
|
105
|
-
alt={`${country} flag`}
|
|
106
|
-
style={{ width: "16px", height: "12px" }}
|
|
107
|
-
/>
|
|
108
|
-
)}
|
|
109
|
-
<span className="text-sm font-medium text-gray-900">{country}</span>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
|
|
114
|
-
{/* Sectoral Scope */}
|
|
115
|
-
{sectoralScope && (
|
|
116
|
-
<div className="flex items-center justify-between py-2 border-b border-gray-200">
|
|
117
|
-
<span className="text-sm text-gray-600">Sectoral Scope</span>
|
|
118
|
-
<span className="text-sm font-medium text-gray-900">
|
|
119
|
-
{sectoralScope}
|
|
120
|
-
</span>
|
|
121
|
-
</div>
|
|
122
|
-
)}
|
|
123
|
-
|
|
124
|
-
{/* Methodology */}
|
|
125
|
-
{methodology && (
|
|
126
|
-
<div className="flex items-center justify-between py-2 border-b border-gray-200">
|
|
127
|
-
<span className="text-sm text-gray-600">Methodology</span>
|
|
128
|
-
<span className="text-sm font-medium text-gray-900">
|
|
129
|
-
{methodology}
|
|
130
|
-
</span>
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
|
-
|
|
134
|
-
{/* SDGs */}
|
|
135
|
-
<div className="pt-2">
|
|
136
|
-
<span className="text-sm text-gray-600">SDGs</span>
|
|
137
|
-
<div className="flex flex-wrap gap-1 mt-2">
|
|
138
|
-
{(sdgs.length > 0 ? sdgs : DEFAULT_SDGS).map((sdg, index) => {
|
|
139
|
-
const sdgConfig = typeof sdg === "number" ? SDG_CONFIG[sdg] : sdg;
|
|
140
|
-
return (
|
|
141
|
-
<img
|
|
142
|
-
key={index}
|
|
143
|
-
src={sdgConfig?.icon}
|
|
144
|
-
alt={sdgConfig?.name}
|
|
145
|
-
style={{ width: "22px", height: "22px", objectFit: "cover" }}
|
|
146
|
-
/>
|
|
147
|
-
);
|
|
148
|
-
})}
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</Card>
|
|
152
|
-
);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Main Widget Catalogue Component
|
|
156
|
-
export default function WidgetCatalogue({
|
|
157
|
-
title = "Project Catalogue",
|
|
158
|
-
projects = [],
|
|
159
|
-
loading = false,
|
|
160
|
-
className = "",
|
|
161
|
-
}) {
|
|
162
|
-
if (loading) {
|
|
163
|
-
return (
|
|
164
|
-
<Widget title={title} className="with-border-header">
|
|
165
|
-
<div className="flex justify-center items-center h-64">
|
|
166
|
-
<Spin size="large" />
|
|
167
|
-
</div>
|
|
168
|
-
</Widget>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
<>
|
|
174
|
-
<Widget title={title} className="with-border-header">
|
|
175
|
-
<div
|
|
176
|
-
style={{
|
|
177
|
-
display: "flex",
|
|
178
|
-
gap: "16px",
|
|
179
|
-
justifyContent: "space-between",
|
|
180
|
-
}}
|
|
181
|
-
>
|
|
182
|
-
{projects.slice(0, 3).map((project, idx) => (
|
|
183
|
-
<div
|
|
184
|
-
key={idx}
|
|
185
|
-
style={{
|
|
186
|
-
flex: "1 1 0", // make each card take equal width
|
|
187
|
-
maxWidth: "calc(33.333% - 16px)",
|
|
188
|
-
}}
|
|
189
|
-
>
|
|
190
|
-
<ProjectCard project={project} />
|
|
191
|
-
</div>
|
|
192
|
-
))}
|
|
193
|
-
</div>
|
|
194
|
-
</Widget>
|
|
195
|
-
</>
|
|
196
|
-
);
|
|
197
|
-
}
|