- added page managebandi;

- improved styles of the pages and sections;
This commit is contained in:
Vitalii Kiiko
2024-08-14 08:28:54 +02:00
parent c09127a675
commit 76b5dd2ece
28 changed files with 945 additions and 56 deletions

View File

@@ -0,0 +1,175 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { uniq } from 'ramda';
// tools
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
import getBandoLabel from '../../../../helpers/getBandoLabel';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
import { InputIcon } from 'primereact/inputicon';
import { Dropdown } from 'primereact/dropdown';
import { Button } from 'primereact/button';
import { Calendar } from 'primereact/calendar';
import { Tag } from 'primereact/tag';
import ProperBandoLabel from '../../../../components/ProperBandoLabel';
import { Link } from 'react-router-dom';
const AllBandiTable = () => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [loading, setLoading] = useState(false);
const [globalFilterValue, setGlobalFilterValue] = useState('');
const [statuses, setStatuses] = useState([]);
useEffect(() => {
// TODO
const items = [
{
name: 'Bando Innovazione 2024',
start_date: '2024-08-08T00:00:00+00:00',
end_date: '2024-08-30T00:00:00+00:00',
submissions: 24,
status: 'publish',
id: 11
},
{
name: 'Bando Sostenibilità 2024',
start_date: '2024-07-28T00:00:00+00:00',
end_date: '2024-08-15T00:00:00+00:00',
submissions: 35,
status: 'draft',
id: 9
},
{
name: 'Bando A',
start_date: '2024-06-28T00:00:00+00:00',
end_date: '2024-06-15T00:00:00+00:00',
submissions: 2,
status: 'closed',
id: 2
}
]
setItems(getFormattedBandiData(items));
setStatuses(uniq(items.map(o => o.status)))
setLoading(false);
initFilters();
}, []);
const getFormattedBandiData = (data) => {
return [...(data || [])].map((d) => {
d.start_date = new Date(d.start_date);
d.end_date = new Date(d.end_date);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('it-IT', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const clearFilter = () => {
initFilters();
};
const onGlobalFilterChange = (e) => {
const value = e.target.value;
let _filters = { ...filters };
_filters['global'].value = value;
setFilters(_filters);
setGlobalFilterValue(value);
};
const initFilters = () => {
setFilters({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
start_date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
end_date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
});
setGlobalFilterValue('');
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined onClick={clearFilter} />
<IconField iconPosition="left">
<InputIcon className="pi pi-search" />
<InputText value={globalFilterValue} onChange={onGlobalFilterChange} placeholder={__('Cerca', 'gepafin')} />
</IconField>
</div>
);
};
const dateStartBodyTemplate = (rowData) => {
return formatDate(rowData.start_date);
};
const dateEndBodyTemplate = (rowData) => {
return formatDate(rowData.end_date);
};
const dateFilterTemplate = (options) => {
return <Calendar value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />;
};
const statusBodyTemplate = (rowData) => {
return <ProperBandoLabel status={rowData.status}/>;
};
const statusFilterTemplate = (options) => {
return <Dropdown value={options.value} options={statuses} onChange={(e) => options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder="Select One" className="p-column-filter" showClear />;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const actionsBodyTemplate = (rowData) => {
return <Link to={`/bandi/${rowData.id}`}>
<Button severity="info" label={__('Modifica', 'gepafin')} icon="pi pi-pencil" size="small" iconPos="right" />
</Link>
}
const header = renderHeader();
return(
<div className="latestBandiTable">
<DataTable value={items} paginator showGridlines rows={10} loading={loading} dataKey="id"
filters={filters}
globalFilterFields={['name', 'status']}
header={header}
emptyMessage="Nothing found." onFilter={(e) => setFilters(e.filters)}>
<Column field="name" header={__('Nome Bando', 'gepafin')} filter filterPlaceholder="Search by name"
style={{ minWidth: '12rem' }}/>
<Column header={__('Data Pubblicazione', 'gepafin')} filterField="start_date" dataType="date"
style={{ minWidth: '10rem' }}
body={dateStartBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Data Scadenza', 'gepafin')} filterField="end_date" dataType="date"
style={{ minWidth: '10rem' }}
body={dateEndBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')} filterMenuStyle={{ width: '14rem' }}
style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter
filterElement={statusFilterTemplate}/>
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default AllBandiTable;

37
src/pages/Bandi/index.js Normal file
View File

@@ -0,0 +1,37 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate } from 'react-router-dom';
// components
import AllBandiTable from './components/AllBandiTable';
import { Button } from 'primereact/button';
const Bandi = () => {
const navigate = useNavigate();
const onGoToCreateNewBando = () => {
navigate('/bandi/new');
}
return(
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Gestione Bandi', 'gepafin')}</h1>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
onClick={onGoToCreateNewBando}
label={__('Crea nuovo bando')} icon="pi pi-plus" iconPos="right"/>
</div>
<AllBandiTable/>
</div>
</div>
)
}
export default Bandi;

115
src/pages/Bando/index.js Normal file
View File

@@ -0,0 +1,115 @@
import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { useParams } from 'react-router-dom';
import { useForm, Controller } from 'react-hook-form';
import { classNames } from 'primereact/utils';
// components
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import getBandoLabel from '../../helpers/getBandoLabel';
import { Button } from 'primereact/button';
const Bando = () => {
const { id } = useParams();
const [data, setData] = useState({});
const {
control,
reset,
handleSubmit,
formState: { errors },
getValues
} = useForm(data);
const onSubmit = data => console.log(data);
console.log(errors, getValues());
const onPublish = () => {
console.log('click onPublish');
}
useEffect(() => {
const parsed = parseInt(id)
const bandoId = !isNaN(parsed) ? parsed : 0;
const data = 0 === bandoId
? {
status: 'draft',
name: null,
description: null
}
: {
name: 'Bando Innovazione 2024',
description: '',
start_date: '2024-08-08T00:00:00+00:00',
end_date: '2024-08-30T00:00:00+00:00',
submissions: 24,
status: 'publish',
id: 11
}
setData(data);
reset();
}, [id]);
return (
<div className="appPage">
<div className="appPage__pageHeader">
<h1>{__('Creazione/Modifica Bando', 'gepafin')}</h1>
<p>
{__('Stato:', 'gepafin')}
<span>{getBandoLabel(data.status)}</span>
</p>
</div>
<div className="appPage__spacer"></div>
{data
? <form className="appForm" onSubmit={handleSubmit(onSubmit)}>
<div className="appForm__field">
<label htmlFor="name" className={classNames({ 'p-error': errors.name })}>
{__('Titolo del Bando', 'gepafin')}*
</label>
<Controller
name="name"
control={control}
defaultValue={data['name']}
rules={{ required: __('È obbligatorio', 'gepafin') }}
render={({ field, fieldState }) => (
<InputText id={field.name}
{...field}
autoFocus
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
</div>
<div className="appForm__field">
<label htmlFor="description" className={classNames({ 'p-error': errors.description })}>
{__('Descrizione', 'gepafin')}*
</label>
<Controller
name="description"
control={control}
defaultValue={data['description']}
rules={{ required: __('È obbligatorio', 'gepafin') }}
render={({ field, fieldState }) => (
<InputTextarea id={field.name}
{...field}
className={classNames({ 'p-invalid': fieldState.invalid })}/>
)}/>
</div>
<div className="appPageSection">
<div className="appPageSection__actions">
<Button
type="submit"
label={__('Salva Bozza', 'gepafin')} icon="pi pi-save" iconPos="right"/>
<Button
onClick={onPublish}
label={__('Pubblica', 'gepafin')} icon="pi pi-upload" iconPos="right"/>
</div>
</div>
</form> : null}
</div>
)
}
export default Bando;

View File

@@ -0,0 +1,176 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
import { uniq } from 'ramda';
// tools
import getBandoLabel from '../../../../helpers/getBandoLabel';
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
import { InputIcon } from 'primereact/inputicon';
import { Dropdown } from 'primereact/dropdown';
import { InputNumber } from 'primereact/inputnumber';
import { Button } from 'primereact/button';
import { Calendar } from 'primereact/calendar';
import { Tag } from 'primereact/tag';
import ProperBandoLabel from '../../../../components/ProperBandoLabel';
const LatestBandiTable = () => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [loading, setLoading] = useState(false);
const [globalFilterValue, setGlobalFilterValue] = useState('');
const [statuses, setStatuses] = useState([]);
useEffect(() => {
// TODO
const items = [
{
name: 'Bando Innovazione 2024',
start_date: '2024-08-08T00:00:00+00:00',
end_date: '2024-08-30T00:00:00+00:00',
submissions: 24,
status: 'publish',
id: 11
},
{
name: 'Bando Sostenibilità 2024',
start_date: '2024-07-28T00:00:00+00:00',
end_date: '2024-08-15T00:00:00+00:00',
submissions: 35,
status: 'publish',
id: 9
},
{
name: 'Bando A',
start_date: '2024-06-28T00:00:00+00:00',
end_date: '2024-06-15T00:00:00+00:00',
submissions: 2,
status: 'closed',
id: 2
}
]
setItems(getFormattedBandiData(items));
setStatuses(uniq(items.map(o => o.status)))
setLoading(false);
initFilters();
}, []);
const getFormattedBandiData = (data) => {
return [...(data || [])].map((d) => {
d.start_date = new Date(d.start_date);
d.end_date = new Date(d.end_date);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('it-IT', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const clearFilter = () => {
initFilters();
};
const onGlobalFilterChange = (e) => {
const value = e.target.value;
let _filters = { ...filters };
_filters['global'].value = value;
setFilters(_filters);
setGlobalFilterValue(value);
};
const initFilters = () => {
setFilters({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
start_date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
end_date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
submissions: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
});
setGlobalFilterValue('');
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined onClick={clearFilter} />
<IconField iconPosition="left">
<InputIcon className="pi pi-search" />
<InputText value={globalFilterValue} onChange={onGlobalFilterChange} placeholder={__('Cerca', 'gepafin')} />
</IconField>
</div>
);
};
const dateStartBodyTemplate = (rowData) => {
return formatDate(rowData.start_date);
};
const dateEndBodyTemplate = (rowData) => {
return formatDate(rowData.end_date);
};
const dateFilterTemplate = (options) => {
return <Calendar value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />;
};
const balanceFilterTemplate = (options) => {
return <InputNumber value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)} />;
};
const statusBodyTemplate = (rowData) => {
return <ProperBandoLabel status={rowData.status}/>;
};
const statusFilterTemplate = (options) => {
return <Dropdown value={options.value} options={statuses} onChange={(e) => options.filterCallback(e.value, options.index)} itemTemplate={statusItemTemplate} placeholder="Select One" className="p-column-filter" showClear />;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)} />;
};
const header = renderHeader();
return(
<div className="latestBandiTable">
<DataTable value={items} paginator showGridlines rows={10} loading={loading} dataKey="id"
filters={filters}
globalFilterFields={['name', 'status']}
header={header}
emptyMessage="Nothing found." onFilter={(e) => setFilters(e.filters)}>
<Column field="name" header={__('Nome Bando', 'gepafin')} filter filterPlaceholder="Search by name"
style={{ minWidth: '12rem' }}/>
<Column header={__('Data Pubblicazione', 'gepafin')} filterField="start_date" dataType="date"
style={{ minWidth: '10rem' }}
body={dateStartBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Data Scadenza', 'gepafin')} filterField="end_date" dataType="date"
style={{ minWidth: '10rem' }}
body={dateEndBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Domande ricevute', 'gepafin')} filterField="submissions" dataType="numeric"
style={{ minWidth: '10rem' }} field="submissions"
filter filterElement={balanceFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')} filterMenuStyle={{ width: '14rem' }}
style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter
filterElement={statusFilterTemplate}/>
</DataTable>
</div>
)
}
export default LatestBandiTable;

View File

@@ -0,0 +1,123 @@
import React, { useState, useEffect} from 'react';
import { __ } from '@wordpress/i18n';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
import { InputIcon } from 'primereact/inputicon';
import { Button } from 'primereact/button';
import { Calendar } from 'primereact/calendar';
const LatestUsersActivityTable = () => {
const [items, setItems] = useState(null);
const [filters, setFilters] = useState(null);
const [loading, setLoading] = useState(false);
const [globalFilterValue, setGlobalFilterValue] = useState('');
useEffect(() => {
// TODO
const bandi = [
{
date: '2024-08-02T00:00:00+14:32',
email: 'mario.rossi@gepafin.it',
action: 'Valutazione Domanda',
details: 'Bando Innovazione 2024 - Domanda #123',
id: 11
},
{
date: '2024-08-01T00:00:00+08:23',
email: 'laura.bianchi@gepafin.it',
action: 'Creazione Bando',
details: 'Nuovo bando "Formazione 2025" in bozza',
id: 9
}
]
setItems(getFormattedBandiData(bandi));
setLoading(false);
initFilters();
}, []);
const getFormattedBandiData = (data) => {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('it-IT', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const clearFilter = () => {
initFilters();
};
const onGlobalFilterChange = (e) => {
const value = e.target.value;
let _filters = { ...filters };
_filters['global'].value = value;
setFilters(_filters);
setGlobalFilterValue(value);
};
const initFilters = () => {
setFilters({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
email: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
});
setGlobalFilterValue('');
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined onClick={clearFilter} />
<IconField iconPosition="left">
<InputIcon className="pi pi-search" />
<InputText value={globalFilterValue} onChange={onGlobalFilterChange} placeholder={__('Cerca', 'gepafin')} />
</IconField>
</div>
);
};
const dateBodyTemplate = (rowData) => {
return formatDate(rowData.date);
};
const dateFilterTemplate = (options) => {
return <Calendar value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)} dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />;
};
const header = renderHeader();
return(
<div className="latestBandiTable">
<DataTable value={items} paginator showGridlines rows={10} loading={loading} dataKey="id"
filters={filters}
globalFilterFields={['name', 'status']}
header={header}
emptyMessage="Nothing found." onFilter={(e) => setFilters(e.filters)}>
<Column header={__('Timestamp', 'gepafin')} filterField="date" dataType="date"
style={{ minWidth: '10rem' }}
body={dateBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="email" header={__('Utente', 'gepafin')} filter filterPlaceholder="Search by email"
style={{ minWidth: '12rem' }}/>
<Column field="action" header={__('Azione', 'gepafin')}/>
<Column field="dettails" header={__('Dettagli', 'gepafin')}/>
</DataTable>
</div>
)
}
export default LatestUsersActivityTable;

View File

@@ -1,12 +1,40 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import { useNavigate } from 'react-router-dom';
// components
import LatestBandiTable from './components/LatestBandiTable';
import LatestUsersActivityTable from './components/LatestUsersActivityTable';
import { Button } from 'primereact/button';
const Dashboard = () => {
const navigate = useNavigate();
const onGoToCreateNewBando = () => {
navigate('/bandi/new');
}
const onGoToUsers = () => {
console.log('onGoToUsers')
}
const onGoToStats = () => {
console.log('onGoToStats')
}
const onGoToSettings = () => {
console.log('onGoToSettings')
}
return(
<div className="appPage">
<h1>{__('Dashboard', 'gepafin')}</h1>
<div className="appPage__pageHeader">
<h1>{__('Dashboard', 'gepafin')}</h1>
</div>
<div className="statsBigBadges">
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Panoramica di Sistema', 'gepafin')}</h2>
<div className="statsBigBadges__grid">
<div className="statsBigBadges__gridItem">
@@ -27,6 +55,43 @@ const Dashboard = () => {
</div>
</div>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Ultimi Bandi Pubblicati', 'gepafin')}</h2>
<LatestBandiTable/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Attività Recenti Utenti', 'gepafin')}</h2>
<LatestUsersActivityTable/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection">
<h2>{__('Collegamenti rapidi', 'gepafin')}</h2>
<div className="appPageSection__actions">
<Button
onClick={onGoToCreateNewBando}
label={__('Crea nuovo bando', 'gepafin')} icon="pi pi-plus" iconPos="right"/>
<Button
disabled={true}
onClick={onGoToUsers}
label={__('Gestione utenti', 'gepafin')} icon="pi pi-users" iconPos="right"/>
<Button
disabled={true}
onClick={onGoToStats}
label={__('Report mensile', 'gepafin')} icon="pi pi-chart-bar" iconPos="right"/>
<Button
disabled={true}
onClick={onGoToSettings}
label={__('Configurazione', 'gepafin')} icon="pi pi-cog" iconPos="right"/>
</div>
</div>
</div>
)
}