Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
portal-apps-manage
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
angular
portal-apps-manage
Commits
11ee1825
Commit
11ee1825
authored
Aug 31, 2025
by
Ooh-Ao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
s
parent
6b8b4df6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
129 additions
and
195 deletions
+129
-195
syncfusion-chart-widget.component.html
...usion-chart-widget/syncfusion-chart-widget.component.html
+14
-1
syncfusion-chart-widget.component.ts
...cfusion-chart-widget/syncfusion-chart-widget.component.ts
+22
-69
syncfusion-datagrid-widget.component.html
...datagrid-widget/syncfusion-datagrid-widget.component.html
+16
-3
syncfusion-datagrid-widget.component.ts
...n-datagrid-widget/syncfusion-datagrid-widget.component.ts
+25
-46
syncfusion-pivot-widget.component.html
...usion-pivot-widget/syncfusion-pivot-widget.component.html
+18
-2
syncfusion-pivot-widget.component.ts
...cfusion-pivot-widget/syncfusion-pivot-widget.component.ts
+34
-74
No files found.
src/app/DPU/widgets/syncfusion-chart-widget/syncfusion-chart-widget.component.html
View file @
11ee1825
<div
class=
"bg-white p-4 rounded-lg shadow-md h-full flex flex-col"
>
<h3
class=
"text-lg font-semibold text-gray-500 mb-4"
>
{{ title }}
</h3>
<ejs-chart
[
title
]="
title
"
[
primaryXAxis
]="
primaryXAxis
"
[
primaryYAxis
]="
primaryYAxis
"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-triangle-fill text-4xl"
></i>
<p
class=
"mt-2"
>
{{ errorMessage }}
</p>
</div>
<!-- Chart -->
<ejs-chart
*
ngIf=
"!isLoading && !hasError"
[
primaryXAxis
]="
primaryXAxis
"
[
primaryYAxis
]="
primaryYAxis
"
>
<e-series-collection>
<e-series
[
dataSource
]="
chartData
"
type=
"Column"
xName=
"x"
yName=
"y"
name=
"Data"
></e-series>
</e-series-collection>
...
...
src/app/DPU/widgets/syncfusion-chart-widget/syncfusion-chart-widget.component.ts
View file @
11ee1825
import
{
Component
,
Input
,
OnInit
,
OnChanges
,
SimpleChanges
}
from
'@angular/core'
;
import
{
Component
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
import
{
ChartModule
,
ColumnSeriesService
,
CategoryService
,
LegendService
,
TooltipService
,
DataLabelService
}
from
'@syncfusion/ej2-angular-charts'
;
import
{
DatasetService
}
from
'../../services/dataset.service'
;
import
{
HttpClient
,
HttpClientModule
}
from
'@angular/common/http'
;
import
{
DatasetModel
}
from
'../../models/widgets.model'
;
import
{
DashboardStateService
}
from
'../../services/dashboard-state.service'
;
import
{
BaseWidgetComponent
}
from
'../base-widget.component'
;
@
Component
({
selector
:
'app-syncfusion-chart-widget'
,
standalone
:
true
,
imports
:
[
CommonModule
,
ChartModule
,
HttpClientModule
],
providers
:
[
ColumnSeriesService
,
CategoryService
,
LegendService
,
TooltipService
,
DataLabelService
,
DatasetService
,
HttpClient
],
imports
:
[
CommonModule
,
ChartModule
],
providers
:
[
ColumnSeriesService
,
CategoryService
,
LegendService
,
TooltipService
,
DataLabelService
],
templateUrl
:
'./syncfusion-chart-widget.component.html'
,
})
export
class
SyncfusionChartWidgetComponent
implements
OnInit
,
OnChanges
{
@
Input
()
title
:
string
=
'Monthly Sales Analysis'
;
// New input for title
@
Input
()
chartData
:
Object
[]
|
undefined
;
// New input for chart data
@
Input
()
config
:
any
;
export
class
SyncfusionChartWidgetComponent
extends
BaseWidgetComponent
{
public
chartData
:
Object
[];
public
primaryXAxis
:
Object
;
public
primaryYAxis
:
Object
;
public
localChartData
:
Object
[];
// Renamed to avoid conflict with input
constructor
(
private
datasetService
:
DatasetService
,
private
http
:
HttpClient
)
{
this
.
localChartData
=
this
.
chartData
||
[
{
month
:
'Jan'
,
sales
:
35
},
{
month
:
'Feb'
,
sales
:
28
},
{
month
:
'Mar'
,
sales
:
34
},
{
month
:
'Apr'
,
sales
:
32
},
{
month
:
'May'
,
sales
:
40
},
{
month
:
'Jun'
,
sales
:
30
},
{
month
:
'Jul'
,
sales
:
35
},
{
month
:
'Aug'
,
sales
:
37
},
{
month
:
'Sep'
,
sales
:
39
},
{
month
:
'Oct'
,
sales
:
30
},
{
month
:
'Nov'
,
sales
:
35
},
{
month
:
'Dec'
,
sales
:
41
}
];
this
.
primaryXAxis
=
{
valueType
:
'Category'
};
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
}
ngOnInit
():
void
{
// If chartData is provided as an input, update localChartData
if
(
!
this
.
config
||
!
this
.
config
.
datasetId
)
{
this
.
chartData
=
[
{
month
:
'Jan'
,
sales
:
35
},
{
month
:
'Feb'
,
sales
:
28
},
{
month
:
'Mar'
,
sales
:
34
},
{
month
:
'Apr'
,
sales
:
32
},
{
month
:
'May'
,
sales
:
40
},
{
month
:
'Jun'
,
sales
:
30
},
{
month
:
'Jul'
,
sales
:
35
},
{
month
:
'Aug'
,
sales
:
37
},
{
month
:
'Sep'
,
sales
:
39
},
{
month
:
'Oct'
,
sales
:
30
},
{
month
:
'Nov'
,
sales
:
35
},
{
month
:
'Dec'
,
sales
:
41
}
];
}
this
.
primaryXAxis
=
{
valueType
:
'Category'
};
onDataUpdate
(
data
:
any
[]):
void
{
this
.
chartData
=
data
.
map
(
item
=>
({
x
:
item
[
this
.
config
.
xField
],
y
:
item
[
this
.
config
.
yField
]
}));
this
.
primaryXAxis
=
{
valueType
:
'Category'
,
title
:
this
.
config
.
xAxisTitle
||
''
};
this
.
primaryYAxis
=
{
title
:
this
.
config
.
yAxisTitle
||
''
};
}
ngOnChanges
(
changes
:
SimpleChanges
):
void
{
if
(
changes
[
'config'
]
&&
this
.
config
&&
this
.
config
.
datasetId
)
{
this
.
loadData
();
}
else
if
(
changes
[
'config'
]
&&
(
!
this
.
config
||
!
this
.
config
.
datasetId
))
{
this
.
chartData
=
[
{
month
:
'Jan'
,
sales
:
35
},
{
month
:
'Feb'
,
sales
:
28
},
{
month
:
'Mar'
,
sales
:
34
},
{
month
:
'Apr'
,
sales
:
32
},
{
month
:
'May'
,
sales
:
40
},
{
month
:
'Jun'
,
sales
:
30
},
{
month
:
'Jul'
,
sales
:
35
},
{
month
:
'Aug'
,
sales
:
37
},
{
month
:
'Sep'
,
sales
:
39
},
{
month
:
'Oct'
,
sales
:
30
},
{
month
:
'Nov'
,
sales
:
35
},
{
month
:
'Dec'
,
sales
:
41
}
];
}
}
loadData
():
void
{
if
(
this
.
config
.
datasetId
)
{
this
.
datasetService
.
getDatasetById
(
this
.
config
.
datasetId
).
subscribe
((
dataset
:
DatasetModel
|
undefined
)
=>
{
if
(
dataset
&&
dataset
.
url
)
{
this
.
http
.
get
<
any
[]
>
(
dataset
.
url
).
subscribe
(
data
=>
{
this
.
chartData
=
data
.
map
(
item
=>
({
x
:
item
[
this
.
config
.
xField
],
y
:
item
[
this
.
config
.
yField
]
}));
if
(
this
.
config
.
title
)
{
this
.
title
=
this
.
config
.
title
;
}
if
(
this
.
config
.
xAxisTitle
)
{
this
.
primaryXAxis
=
{
...
this
.
primaryXAxis
,
title
:
this
.
config
.
xAxisTitle
};
}
if
(
this
.
config
.
yAxisTitle
)
{
this
.
primaryYAxis
=
{
...
this
.
primaryYAxis
,
title
:
this
.
config
.
yAxisTitle
};
}
});
}
});
}
onReset
():
void
{
this
.
title
=
'Syncfusion Chart (Default)'
;
this
.
chartData
=
[
{
x
:
'Jan'
,
y
:
35
},
{
x
:
'Feb'
,
y
:
28
},
{
x
:
'Mar'
,
y
:
34
},
{
x
:
'Apr'
,
y
:
32
},
{
x
:
'May'
,
y
:
40
},
{
x
:
'Jun'
,
y
:
30
},
];
this
.
primaryXAxis
=
{
valueType
:
'Category'
,
title
:
'Month'
};
this
.
primaryYAxis
=
{
title
:
'Sales'
};
}
}
src/app/DPU/widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component.html
View file @
11ee1825
<div
class=
"h-full"
>
<div
class=
"h-full
flex flex-col
"
>
<h5
class=
"text-lg font-semibold text-gray-600 mb-2"
>
{{ title }}
</h5>
<ejs-grid
[
dataSource
]="
data
"
[
allowPaging
]="
true
"
[
pageSettings
]="
pageSettings
"
[
allowSorting
]="
true
"
[
allowFiltering
]="
true
"
[
allowGrouping
]="
true
"
>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-triangle-fill text-4xl"
></i>
<p
class=
"mt-2"
>
{{ errorMessage }}
</p>
</div>
<!-- Grid -->
<ejs-grid
*
ngIf=
"!isLoading && !hasError"
[
dataSource
]="
data
"
[
allowPaging
]="
true
"
[
pageSettings
]="
pageSettings
"
[
allowSorting
]="
true
"
[
allowFiltering
]="
true
"
[
allowGrouping
]="
true
"
class=
"flex-grow"
>
<e-columns>
<e-column
*
ngFor=
"let col of columns"
[
field
]="
col
.
field
"
[
headerText
]="
col
.
headerText
"
[
width
]="
col
.
width
"
></e-column>
</e-columns>
</ejs-grid>
</div>
</div>
src/app/DPU/widgets/syncfusion-datagrid-widget/syncfusion-datagrid-widget.component.ts
View file @
11ee1825
import
{
Component
,
Input
,
OnInit
,
OnChanges
,
SimpleChanges
,
ChangeDetectorRef
}
from
'@angular/core'
;
import
{
Component
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
import
{
HttpClient
}
from
'@angular/common/http'
;
import
{
GridModule
,
PageService
,
SortService
,
FilterService
,
GroupService
}
from
'@syncfusion/ej2-angular-grids'
;
import
{
DatasetService
}
from
'../../services/dataset.service'
;
import
{
DatasetModel
}
from
'../../models/widgets.model'
;
import
{
DashboardStateService
}
from
'../../services/dashboard-state.service'
;
import
{
BaseWidgetComponent
}
from
'../base-widget.component'
;
@
Component
({
selector
:
'app-syncfusion-datagrid-widget'
,
...
...
@@ -13,55 +11,36 @@ import { DatasetModel } from '../../models/widgets.model';
providers
:
[
PageService
,
SortService
,
FilterService
,
GroupService
],
templateUrl
:
'./syncfusion-datagrid-widget.component.html'
,
})
export
class
SyncfusionDatagridWidgetComponent
implements
OnInit
,
OnChanges
{
@
Input
()
config
:
any
;
export
class
SyncfusionDatagridWidgetComponent
extends
BaseWidgetComponent
{
public
data
:
object
[]
=
[];
public
columns
:
any
[]
=
[];
public
title
:
string
=
'Data Grid'
;
public
pageSettings
:
Object
=
{
pageSize
:
6
};
public
pageSettings
:
Object
=
{
pageSize
:
10
};
constructor
(
private
datasetService
:
DatasetService
,
private
http
:
HttpClient
,
private
cdr
:
ChangeDetectorRef
)
{}
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
}
ngOnInit
():
void
{
if
(
!
this
.
config
||
!
this
.
config
.
datasetId
)
{
this
.
data
=
[];
this
.
columns
=
[];
onDataUpdate
(
data
:
any
[]):
void
{
this
.
data
=
data
;
if
(
this
.
config
.
columns
&&
this
.
config
.
columns
.
length
>
0
)
{
this
.
columns
=
this
.
config
.
columns
;
}
else
if
(
this
.
data
.
length
>
0
)
{
// Auto-generate columns if not provided in config
this
.
columns
=
Object
.
keys
(
this
.
data
[
0
]).
map
(
key
=>
({
field
:
key
,
headerText
:
this
.
formatHeader
(
key
),
width
:
150
}));
}
this
.
loadData
();
}
ngOnChanges
(
changes
:
SimpleChanges
):
void
{
if
(
changes
[
'config'
]
&&
this
.
config
&&
this
.
config
.
datasetId
)
{
this
.
loadData
();
}
else
if
(
changes
[
'config'
]
&&
(
!
this
.
config
||
!
this
.
config
.
datasetId
))
{
this
.
data
=
[];
this
.
columns
=
[];
}
onReset
():
void
{
this
.
title
=
'Data Grid (Default)'
;
this
.
data
=
[];
this
.
columns
=
[{
field
:
'Message'
,
headerText
:
'Please select a dataset'
}];
}
loadData
():
void
{
if
(
this
.
config
.
datasetId
)
{
this
.
datasetService
.
getDatasetById
(
this
.
config
.
datasetId
).
subscribe
((
dataset
:
DatasetModel
|
undefined
)
=>
{
if
(
dataset
&&
dataset
.
url
)
{
this
.
http
.
get
<
object
[]
>
(
dataset
.
url
).
subscribe
(
data
=>
{
this
.
data
=
data
;
if
(
this
.
config
.
columns
)
{
this
.
columns
=
this
.
config
.
columns
;
}
else
if
(
this
.
data
.
length
>
0
)
{
// Auto-generate columns if not provided in config
this
.
columns
=
Object
.
keys
(
this
.
data
[
0
]).
map
(
key
=>
({
field
:
key
,
headerText
:
key
}));
}
if
(
this
.
config
.
title
)
{
this
.
title
=
this
.
config
.
title
;
}
this
.
cdr
.
markForCheck
();
});
}
});
}
else
{
this
.
data
=
[];
this
.
columns
=
[];
}
private
formatHeader
(
key
:
string
):
string
{
return
key
.
replace
(
/_/g
,
' '
).
replace
(
/
\b\w
/g
,
char
=>
char
.
toUpperCase
());
}
}
src/app/DPU/widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component.html
View file @
11ee1825
<ejs-pivotview
[
dataSourceSettings
]="
dataSourceSettings
"
[
allowCalculatedField
]="
true
"
[
showFieldList
]="
true
"
[
showToolbar
]="
true
"
>
</ejs-pivotview>
<div
class=
"h-full flex flex-col"
>
<h5
class=
"text-lg font-semibold text-gray-600 mb-2"
>
{{ title }}
</h5>
<!-- Loading State -->
<div
*
ngIf=
"isLoading"
class=
"flex justify-center items-center h-full"
>
<div
class=
"animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"
></div>
</div>
<!-- Error State -->
<div
*
ngIf=
"hasError"
class=
"flex flex-col justify-center items-center h-full text-red-500"
>
<i
class=
"bi bi-exclamation-triangle-fill text-4xl"
></i>
<p
class=
"mt-2"
>
{{ errorMessage }}
</p>
</div>
<!-- Pivot View -->
<ejs-pivotview
*
ngIf=
"!isLoading && !hasError"
[
dataSourceSettings
]="
dataSourceSettings
"
[
allowCalculatedField
]="
true
"
[
showFieldList
]="
true
"
[
showToolbar
]="
true
"
class=
"flex-grow"
>
</ejs-pivotview>
</div>
src/app/DPU/widgets/syncfusion-pivot-widget/syncfusion-pivot-widget.component.ts
View file @
11ee1825
import
{
Component
,
Input
,
OnChanges
,
OnInit
,
SimpleChanges
}
from
'@angular/core'
;
import
{
Component
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
import
{
PivotViewModule
,
IDataSet
,
FieldListService
,
CalculatedFieldService
,
ToolbarService
,
GroupingBarService
,
ConditionalFormattingService
,
LoadEventArgs
}
from
'@syncfusion/ej2-angular-pivotview'
;
import
{
DatasetModel
}
from
'../../models/widgets.model'
;
import
{
DatasetService
}
from
'../../services/dataset.service'
;
import
{
HttpClient
}
from
'@angular/common/http'
;
import
{
PivotViewModule
,
IDataSet
,
FieldListService
,
CalculatedFieldService
,
ToolbarService
,
GroupingBarService
,
ConditionalFormattingService
}
from
'@syncfusion/ej2-angular-pivotview'
;
import
{
DashboardStateService
}
from
'../../services/dashboard-state.service'
;
import
{
BaseWidgetComponent
}
from
'../base-widget.component'
;
@
Component
({
selector
:
'app-syncfusion-pivot-widget'
,
...
...
@@ -12,83 +11,44 @@ import { HttpClient } from '@angular/common/http';
providers
:
[
FieldListService
,
CalculatedFieldService
,
ToolbarService
,
GroupingBarService
,
ConditionalFormattingService
],
templateUrl
:
'./syncfusion-pivot-widget.component.html'
,
})
export
class
SyncfusionPivotWidgetComponent
implements
OnInit
,
OnChanges
{
@
Input
()
config
:
any
;
export
class
SyncfusionPivotWidgetComponent
extends
BaseWidgetComponent
{
public
dataSourceSettings
:
{
dataSource
:
IDataSet
[];
expandAll
:
boolean
;
rows
:
any
[];
columns
:
any
[];
values
:
any
[];
filters
:
any
[];
};
public
dataSource
:
IDataSet
[];
public
dataSourceSettings
:
Object
;
constructor
(
protected
override
dashboardStateService
:
DashboardStateService
)
{
super
(
dashboardStateService
);
}
constructor
(
private
datasetService
:
DatasetService
,
private
http
:
HttpClient
)
{
this
.
dataSource
=
[];
onDataUpdate
(
data
:
IDataSet
[]):
void
{
this
.
dataSourceSettings
=
{
dataSource
:
this
.
dataSource
,
expandAll
:
false
,
rows
:
[],
columns
:
[],
values
:
[],
filters
:
[],
dataSource
:
data
,
expandAll
:
this
.
config
.
expandAll
||
false
,
rows
:
this
.
config
.
rows
||
[],
columns
:
this
.
config
.
columns
||
[],
values
:
this
.
config
.
values
||
[],
filters
:
this
.
config
.
filters
||
[],
};
}
ngOnInit
():
void
{
if
(
!
this
.
config
||
!
this
.
config
.
datasetId
)
{
this
.
dataSource
=
[
{
'Sold'
:
31
,
'Amount'
:
52824
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2015'
},
{
'Sold'
:
51
,
'Amount'
:
91915
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2016'
},
{
'Sold'
:
90
,
'Amount'
:
173155
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2017'
},
];
this
.
dataSourceSettings
=
{
dataSource
:
this
.
dataSource
,
expandAll
:
false
,
rows
:
[{
name
:
'Country'
}],
columns
:
[{
name
:
'Year'
}],
values
:
[{
name
:
'Sold'
},
{
name
:
'Amount'
}],
filters
:
[],
};
}
}
ngOnChanges
(
changes
:
SimpleChanges
):
void
{
if
(
changes
[
'config'
]
&&
this
.
config
&&
this
.
config
.
datasetId
)
{
this
.
loadData
();
}
else
if
(
changes
[
'config'
]
&&
(
!
this
.
config
||
!
this
.
config
.
datasetId
))
{
this
.
dataSource
=
[
onReset
():
void
{
this
.
title
=
'Pivot Table (Default)'
;
this
.
dataSourceSettings
=
{
dataSource
:
[
{
'Sold'
:
31
,
'Amount'
:
52824
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2015'
},
{
'Sold'
:
51
,
'Amount'
:
91915
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2016'
},
{
'Sold'
:
90
,
'Amount'
:
173155
,
'Country'
:
'France'
,
'Products'
:
'Mountain Bikes'
,
'Year'
:
'FY 2017'
},
];
this
.
dataSourceSettings
=
{
dataSource
:
this
.
dataSource
,
expandAll
:
false
,
rows
:
[{
name
:
'Country'
}],
columns
:
[{
name
:
'Year'
}],
values
:
[{
name
:
'Sold'
},
{
name
:
'Amount'
}],
filters
:
[],
};
}
}
loadData
():
void
{
if
(
this
.
config
.
datasetId
)
{
this
.
datasetService
.
getDatasetById
(
this
.
config
.
datasetId
).
subscribe
((
dataset
:
DatasetModel
|
undefined
)
=>
{
if
(
dataset
&&
dataset
.
url
)
{
this
.
http
.
get
<
any
[]
>
(
dataset
.
url
).
subscribe
(
data
=>
{
this
.
dataSource
=
data
;
this
.
dataSourceSettings
=
{
dataSource
:
this
.
dataSource
,
expandAll
:
this
.
config
.
expandAll
||
false
,
rows
:
this
.
config
.
rows
||
[],
columns
:
this
.
config
.
columns
||
[],
values
:
this
.
config
.
values
||
[],
filters
:
this
.
config
.
filters
||
[],
};
if
(
this
.
config
.
title
)
{
// Assuming there's a title property in the component for the pivot table
// this.title = this.config.title;
}
});
}
});
}
],
expandAll
:
false
,
rows
:
[{
name
:
'Country'
}],
columns
:
[{
name
:
'Year'
}],
values
:
[{
name
:
'Sold'
},
{
name
:
'Amount'
}],
filters
:
[],
};
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment