Add collection and new vocab + mess with repository

This commit is contained in:
Nicolò P 2024-11-04 17:16:16 +01:00
parent f8e470b096
commit 0a41ff4a7b
28 changed files with 2599 additions and 38 deletions

View File

@ -44,10 +44,12 @@ if (location.pathname.includes('login')) {
if (! location.pathname.includes('login')) {
const vocabs = document.querySelector('#vocabs');
const records = document.querySelector('#records');
const userMenu = document.querySelector('.dropdown-trigger');
const userCaret = document.querySelector('#user-caret');
const forVocabs = document.querySelector('#for-vocabs')
const forRecords = document.querySelector('#for-records')
if (forVocabs) {
forVocabs.addEventListener('click', function () {
@ -63,6 +65,20 @@ if (! location.pathname.includes('login')) {
});
}
if (forRecords) {
forRecords.addEventListener('click', function () {
records.classList.toggle('is-hidden');
if (this.firstElementChild.classList.contains('fa-angle-right')) {
this.firstElementChild.classList.remove('fa-angle-right');
this.firstElementChild.classList.add('fa-angle-down');
} else {
this.firstElementChild.classList.remove('fa-angle-down');
this.firstElementChild.classList.add('fa-angle-right');
}
});
}
userMenu.addEventListener('click', function () {
document.querySelector('.dropdown').classList.toggle('is-active');
if (userCaret.classList.contains('fa-caret-down')) {

View File

@ -1,6 +1,6 @@
@import url('../vendor/bulma/css/bulma.min.css');
@import url('../fonts/stylesheet.css');
@import url('../fontawesome-free-6.6.0-web/css/all.min.css');
@import url('../vendor/bulma/css/bulma.min.css');
:root {
--arcoa-blue: rgba(66,135,199,0.98);
@ -15,13 +15,11 @@ body {
min-height: 100vh;
overflow-y: auto;
}
#vocabs a {
#vocabs a,
#records a {
color: #fff;
}
/* For dev only*/
.sf-toolbar-clearer {
display: none !important;
}
.table.record td,
.table.record th {
@ -29,5 +27,13 @@ body {
}
.table.record th {
min-width: 220px;
min-width: 240px;
}
.table td {
vertical-align: middle;
}
.results tr > th {
text-align: center !important;
}

View File

@ -7,10 +7,13 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/doctrine-orm": "^4.0",
"api-platform/symfony": "^4.0",
"doctrine/dbal": "^3",
"doctrine/doctrine-bundle": "^2.13",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.3",
"nelmio/cors-bundle": "^2.5",
"phpdocumentor/reflection-docblock": "^5.4",
"phpstan/phpdoc-parser": "^1.33",
"symfony/asset": "7.1.*",

1480
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,4 +13,6 @@ return [
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
];

View File

@ -0,0 +1,7 @@
api_platform:
title: Hello API Platform
version: 1.0.0
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']

View File

@ -4,7 +4,9 @@ framework:
csrf_protection: true
# Note that the session will be started ONLY if you read or write from it.
session: true
session:
enabled: true
cookie_lifetime: 3600
#esi: true
#fragments: true

View File

@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': null

View File

@ -38,6 +38,9 @@ security:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
- { path: ^/bibliography, roles: ROLE_USER }
- { path: ^/collection, roles: ROLE_USER }
- { path: ^/object, roles: ROLE_USER }
- { path: ^/site, roles: ROLE_USER }
when@test:
security:

View File

@ -0,0 +1,4 @@
api_platform:
resource: .
type: api_platform
prefix: /api

View File

@ -3,6 +3,7 @@
namespace App\Controller;
use App\Entity\Bibliography;
use App\Form\BibliographyType;
//use App\Security\Voter\VocabVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -23,10 +24,18 @@ class BibliographyController extends AbstractController
}
#[Route('/bibliography', name: 'app_bibliography_landing')]
public function landing(): Response
public function landing(EntityManagerInterface $em): Response
{
$repo = $em->getRepository(Bibliography::class);
$records = $repo->findBy([], ['modifiedAt' => 'DESC']);
$count = count($records);
$records = array_slice($records, 0, 15);
return $this->render('bibliography/landing.html.twig', [
'controller_name' => 'BibliographyController',
'records' => $records,
'count' => $count,
]);
}
@ -38,11 +47,30 @@ class BibliographyController extends AbstractController
]);
}
#[Route('/bibliography/add', name: 'app_bibliography_add')]
/**
* @todo Permissions with voter
*/
#[Route('/bibliography/add', name: 'app_bibliography_create')]
public function add(): Response
{
return $this->render('bibliography/add.html.twig', [
$form = $this->createForm(BibliographyType::class);
return $this->render('bibliography/create.html.twig', [
'controller_name' => 'BibliographyController',
'form' => $form,
]);
}
/**
* @todo Permissions!
*/
#[Route('/bibliography/delete/{id<\d+>}', name: 'app_bibliography_del')]
public function delete(Bibliography $bibliography, EntityManagerInterface $em): Response
{
$em->remove($bibliography);
$em->flush();
$this->addFlash('notice', 'Term deleted successfully');
return $this->redirectToRoute('app_bibliography_landing');
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Controller;
use App\Entity\Collection;
use App\Entity\Bibliography;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CollectionController extends AbstractController
{
#[Route('/collection/{id<\d+>}', name: 'app_collection')]
public function index(Collection $collection, EntityManagerInterface $em): Response
{
$bibliographies = $em->getRepository(Bibliography::class)->findAllCollection($collection->getId());
$collection->setBibliographies($bibliographies);
return $this->render('collection/index.html.twig', [
'controller_name' => 'CollectionController',
'record' => $collection,
]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Controller;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use App\Entity\VocabObjectType;
/**
* @todo Pagination
*/
class VocabObjectTypeController extends AbstractController
{
#[Route('/vocabs/object_type', name: 'app_vocab_object_type')]
public function index(EntityManagerInterface $em): Response
{
$roles = $this->getUser()->getRoles();
if (in_array('ROLE_READER', $roles)) {
$this->addFlash('warning', 'Only editors, revisors and administrators can view vocabularies');
return $this->redirectToRoute('app_home');
}
$terms = $em->getRepository(VocabObjectType::class)
->findBy([], ['term' => 'ASC']);
return $this->render('vocab_object_type/index.html.twig', [
'controller_name' => 'VocabObjectTypeController',
'terms' => $terms
]);
}
}

View File

@ -2,14 +2,14 @@
namespace App\Entity;
//use App\Repository\UserRepository;
use App\Repository\BibliographyRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;
use App\RecordStatus;
use Doctrine\Common\Collections\Collection as DoctrineCollection;
#[ORM\Entity()]
#[ORM\Entity(repositoryClass: BibliographyRepository::class)]
#[ORM\Table(name: 'bibliography')]
class Bibliography
{
@ -42,6 +42,20 @@ class Bibliography
#[ORM\Column(length: 100, name: 'creator')]
private ?string $creator = null;
#[ORM\JoinTable(name: 'rel_riferimento_collezione')]
#[ORM\JoinColumn(name: 'Bibliografia_id_bib', referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name: 'Collezione_id_coll', referencedColumnName: 'id')]
#[ORM\ManyToMany(targetEntity: Collection::class)]
private DoctrineCollection $collections;
private DoctrineCollection $documents;
private DoctrineCollection $objects;
private DoctrineCollection $persons;
private DoctrineCollection $sites;
public function getId(): ?int
{
return $this->id;
@ -143,4 +157,16 @@ class Bibliography
return $this;
}
public function getCollections(): ?DoctrineCollection
{
return $this->collections;
}
public function setCollections(DoctrineCollection $collections): static
{
$this->collections = $collections;
return $this;
}
}

295
src/Entity/Collection.php Normal file
View File

@ -0,0 +1,295 @@
<?php
namespace App\Entity;
//use App\Repository\UserRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;
use App\RecordStatus;
use Doctrine\Common\Collections\Collection as DoctrineCollection;
#[ORM\Entity()]
#[ORM\Table(name: 'collection')]
class Collection
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(name: 'id')]
private ?int $id = null;
#[ORM\Column(name: 'stato')]
private ?int $status = null;
#[ORM\Column(name: 'modif')]
private ?DateTimeImmutable $modifiedAt = null;
#[ORM\Column(name: 'tit_coll', type: Types::TEXT)]
private ?string $title = null;
#[ORM\Column(name: 'data_coll', type: Types::TEXT)]
private ?string $chronology = null;
#[ORM\Column(name: 'inizio_coll', type: Types::SMALLINT)]
private ?int $startDate = null;
#[ORM\Column(name: 'fine_coll', type: Types::SMALLINT)]
private ?int $endDate = null;
#[ORM\Column(name: 'desc_coll', type: Types::TEXT)]
private ?string $description = null;
#[ORM\Column(name: 'desc_br_coll', type: Types::TEXT)]
private ?string $shortDescription = null;
#[ORM\Column(name: 'id_est_coll', type: Types::TEXT)]
private ?string $externalIdentifier = null;
#[ORM\Column(name: 'link_coll', type: Types::TEXT)]
private ?string $link = null;
#[ORM\Column(name: 'sogg_coll', type: Types::TEXT)]
private ?string $subjectHeadings = null;
#[ORM\Column(name: 'uri_coll', type: Types::TEXT)]
private ?string $uri = null;
#[ORM\Column(name: 'resp', length: 100)]
private ?string $owner = null;
#[ORM\Column(name: 'note_coll', type: Types::TEXT)]
private ?string $notes = null;
#[ORM\Column(length: 100, name: 'editor')]
private ?string $editor = null;
#[ORM\Column(length: 100, name: 'creator')]
private ?string $creator = null;
// TODO: These are references to vocabs
#[ORM\Column(name: 'dir_aut_coll')]
private ?int $authorRights = null;
#[ORM\Column(name: 'dir_acc_coll')]
private ?int $accessRights = null;
#[ORM\Column(name: 'lic_coll')]
private ?int $license = null;
#[ORM\JoinTable(name: 'rel_riferimento_collezione')]
#[ORM\JoinColumn(name: 'Collezione_id_coll', referencedColumnName: 'id')]
#[ORM\InverseJoinColumn(name: 'Bibliografia_id_bib', referencedColumnName: 'id')]
#[ORM\ManyToMany(targetEntity: Bibliography::class)]
private DoctrineCollection $bibliographies;
public function getId(): ?int
{
return $this->id;
}
public function getStatus(): ?string
{
$status = RecordStatus::tryFrom($this->status);
return $status->toString();
}
public function setStatus(int $status): static
{
$this->status = $status;
return $this;
}
public function getModifiedAt(): ?DateTimeImmutable
{
return $this->modifiedAt;
}
public function setModifiedAt(DateTimeImmutable $modifiedAt): static
{
$this->modifiedAt = $modifiedAt;
return $this;
}
public function getOwner(): ?string
{
return $this->owner;
}
public function setOwner(string $owner): static
{
$this->owner = $owner;
return $this;
}
public function getEditor(): ?string
{
return $this->editor;
}
public function setEditor(string $editor): static
{
$this->editor = $editor;
return $this;
}
public function getCreator(): ?string
{
return $this->creator;
}
public function setCreator(string $creator): static
{
$this->creator = $creator;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getChronology(): ?string
{
return $this->chronology;
}
public function setChronology(string $chronology): static
{
$this->chronology = $chronology;
return $this;
}
public function getStartDate(): ?int
{
return $this->startDate;
}
public function setStartDate(int $startDate): static
{
$this->startDate = $startDate;
return $this;
}
public function getEndDate(): ?int
{
return $this->endDate;
}
public function setEndDate(int $endDate): static
{
$this->endDate = $endDate;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): static
{
$this->description = $description;
return $this;
}
public function getShortDescription(): ?string
{
return $this->shortDescription;
}
public function setShortDescription(string $shortDescription): static
{
$this->shortDescription = $shortDescription;
return $this;
}
public function getExternalIdentifier(): ?string
{
return $this->externalIdentifier;
}
public function setExternalIdentifier(string $externalIdentifier): static
{
$this->externalIdentifier = $externalIdentifier;
return $this;
}
public function getLink(): ?string
{
return $this->link;
}
public function setLink(string $link): static
{
$this->link = $link;
return $this;
}
public function getSubjectHeadings(): ?string
{
return $this->link;
}
public function setSubjectHeadings(string $subjectHeadings): static
{
$this->subjectHeadings = $subjectHeadings;
return $this;
}
public function getUri(): ?string
{
return $this->uri;
}
public function setUri(string $uri): static
{
$this->uri = $uri;
return $this;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function setNotes(string $notes): static
{
$this->notes = $notes;
return $this;
}
public function getBibliographies(): ?DoctrineCollection
{
return $this->bibliographies;
}
public function setBibliographies(DoctrineCollection $bibliographies): static
{
$this->bibliographies = $bibliographies;
return $this;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Entity;
//use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity()]
#[ORM\Table(name: 'lis_tipologia_oggetto')]
class VocabObjectType implements \App\VocabInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(name: 'id_lis_tip_ogg')]
private ?int $id = null;
#[ORM\Column(length: 80, name: 'lis_tip_ogg')]
private ?string $term = null;
public function getId(): ?int
{
return $this->id;
}
public function getTerm(): ?string
{
return $this->term;
}
public function setTerm(string $term): static
{
$this->term = $term;
return $this;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Form;
use App\Entity\Bibliography;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BibliographyType extends AbstractType
{
/**
* @todo Create status choices from enum
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('status', ChoiceType::class, [
'choices' => [
'-- Select status --' => '',
'Draft' => 1,
'Complete' => 2,
'Unindexed' => 3,
'Published' => 4,
],
'label' => 'Status (*)'
])
->add(
'editor',
TextType::class,
[
'label' => 'Editor(s) (*)'
]
)
->add('citation', TextType::class, ['label' => 'Citation (*)'])
->add('reference', TextareaType::class, ['label' => 'Reference (*)'])
->add('notes', TextareaType::class, ['label' => 'Editorial notes', 'required' => false])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Bibliography::class,
]);
}
}

View File

@ -5,6 +5,8 @@ namespace App\Repository;
use App\Entity\Bibliography;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
/**
* @extends ServiceEntityRepository<Bibliography>
@ -15,5 +17,24 @@ class BibliographyRepository extends ServiceEntityRepository
{
parent::__construct($registry, Bibliography::class);
}
public function findAllCollection(int $collectionId): ?ArrayCollection
{
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addRootEntityFromClassMetadata('App\Entity\Bibliography', 'b');
$query = $this->getEntityManager()->createNativeQuery(
"SELECT id, stato, editor, cit_bib, rif_bib FROM bibliography b
JOIN rel_riferimento_collezione
ON Bibliografia_id_bib = id
WHERE Collezione_id_coll = :collId",
$rsm
);
$query->setParameter('collId', $collectionId);
$bibliographies = new ArrayCollection($query->getResult());
return $bibliographies;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Repository;
use App\Entity\Collection;
use App\Repository\BibliographyRepository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Collection>
*/
class CollectionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Collection::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\VocabObjectType;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<VocabFuncContext>
*/
class VocabObjectTypeRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, VocabObjectType::class);
}
}

View File

@ -1,4 +1,18 @@
{
"api-platform/symfony": {
"version": "4.0",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "4.0",
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
},
"files": [
"config/packages/api_platform.yaml",
"config/routes/api_platform.yaml",
"src/ApiResource/.gitignore"
]
},
"doctrine/doctrine-bundle": {
"version": "2.13",
"recipe": {
@ -26,6 +40,18 @@
"migrations/.gitignore"
]
},
"nelmio/cors-bundle": {
"version": "2.5",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "1.5",
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
},
"files": [
"config/packages/nelmio_cors.yaml"
]
},
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
@ -248,6 +274,15 @@
"templates/base.html.twig"
]
},
"symfony/uid": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
}
},
"symfony/ux-turbo": {
"version": "v2.21.0"
},

View File

@ -0,0 +1,72 @@
{% extends 'data_entry.html.twig' %}
{% block title %}Bibliography - Add new | ArCOA{% endblock %}
{% block rightpanel %}
<div class="container" style="max-width: 60vw">
<h1 class="is-size-1 mt-0 has-text-centered">Bibliography</h1>
<h2 class="is-size-3 mt-3 has-text-centered">Add new record</h2>
<p class="pt-4 pb-4 has-text-link has-text-weight-bold">Fields marked with (*) are mandatory</p>
<div class="card p-5 has-background-light">
{{ form_start(form) }}
<div class="field">
<label class="label">
{{ form_label(form.status) }}
</label>
<div class="select">
{{ form_widget(form.status) }}
</div>
</div>
<div class="field">
<label class="label">
{{ form_label(form.editor) }}
</label>
<div class="control">
{{
form_widget(
form.editor,
{'attr': {'class': 'input', 'placeholder': 'Name of the editor (mandatory)'}}
)
}}
</div>
</div>
<div class="field">
<label class="label">
{{ form_label(form.citation) }}
</label>
<div class="control">
{{ form_widget(form.citation, {'attr': {'class': 'input'}}) }}
</div>
</div>
<div class="field">
<label class="label">
{{ form_label(form.reference) }}
</label>
<div class="control">
{{ form_widget(form.reference, {'attr': {'class': 'textarea'}}) }}
</div>
</div>
<div class="field">
<label class="label">
{{ form_label(form.notes) }}
</label>
<div class="control">
{{ form_widget(form.notes, {'attr': {'class': 'textarea'}}) }}
</div>
</div>
<div class="field is-grouped is-grouped-right mt-5">
<div class="control">
<button type="submit" class="button is-link">
Save
<span class="icon ml-3">
<i class="fa fa-save"></i>
</span>
</button>
</div>
</div>
{{ form_end(form) }}
</div>
</div>
<script type="text/javascript" defer></script>
{% endblock %}

View File

@ -1,6 +1,6 @@
{% extends 'data_entry.html.twig' %}
{% block title %}Bibliography | ArCOA{% endblock %}
{% block title %}Bibliography - {{ record.citation }} | ArCOA{% endblock %}
{% block rightpanel %}
<div class="container" style="max-width: 60vw">
@ -16,6 +16,7 @@
<p><strong>Editor:</strong> {{ record.editor }}</p>
</div>
</article>
<div class="card p-5">
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<div class="columns">
@ -27,12 +28,18 @@
<i class="fa fa-edit"></i>
</span>
</button>
<button class="button is-danger ml-2">
<button class="button is-link">
Copy
<span class="icon ml-2">
<i class="fa fa-copy"></i>
</span>
</button>
<a href="{{ path('app_bibliography_del', {'id' : record.id}) }}" class="button is-danger" id="del-record">
Delete
<span class="icon ml-2">
<i class="fa fa-trash"></i>
</span>
</button>
</a>
</div>
</div>
{% endif %}
@ -57,5 +64,54 @@
</div>
</div>
</div>
<script type="text/javascript" defer></script>
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<span class="icon is-large has-text-warning">
<i class="fa fa-warning fa-2x"></i>
</span>
<p class="modal-card-title has-text-danger pl-2"><strong>Delete record?</strong></p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<p class="is-size-5">This record will be permanently deleted. Proceed?</p>
</section>
<footer class="modal-card-foot">
<div class="buttons is-right">
<button class="button is-link" id="confirm-del">Confirm</button>
<button class="button is-light" id="cancel">Cancel</button>
</div>
</footer>
</div>
</div>
<script type="text/javascript" defer>
const del = document.querySelector('#del-record');
const delPath = del.href;
del.addEventListener('click', event => {
event.preventDefault();
const modal = document.querySelector('.modal');
modal.classList.add('is-active');
modal.querySelector('.delete').addEventListener('click', () => {
modal.classList.remove('is-active');
});
modal.querySelector('.modal-background').addEventListener('click', () => {
modal.classList.remove('is-active');
});
modal.querySelector('#cancel').addEventListener('click', () => {
modal.classList.remove('is-active');
});
// Proceed with deletion...
modal.querySelector('#confirm-del').addEventListener('click', () => {
location.href = delPath;
});
});
</script>
{% endblock %}

View File

@ -7,19 +7,25 @@
<h1 class="is-size-1 mt-0 has-text-centered">Bibliography</h1>
<h2 class="is-size-3 mt-3 has-text-centered">Choose action</h2>
{% for message in app.flashes('notice') %}
<div class=" mt-4 notification is-success">
<button class="delete"></button>
{{ message }}
</div>
{% endfor %}
<div class="card p-5 mt-6 pt-6 pb-6">
<div class="columns">
<div class="column is-half has-text-centered">
<a href="{{ path('app_bibliography_search') }}" class="button is-large">
<button class="button is-medium">
Search
<span class="icon ml-2">
<i class="fa fa-search"></i>
</span>
</a>
</button>
</div>
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') or is_granted('ROLE_EDITOR') %}
<div class="column has-text-centered">
<a href="{{ path('app_bibliography_add') }}" class="button is-link is-large">
<a href="{{ path('app_bibliography_create') }}" class="button is-link is-medium">
Add new
<span class="icon ml-2">
<i class="fa fa-plus"></i>
@ -29,6 +35,54 @@
{% endif %}
</div>
</div>
<h3 class="has-text-centered is-size-4 mt-6">Records</h3>
<p class="pt-4 pb-4"><strong>{{ count }} result(s) found</strong></p>
<table class="table is-hoverable is-fullwidth mt-5 has-text-centered results">
<tr>
<th>ID</th>
<th>Citation</th>
<th>Status</th>
<th>Editor</th>
<th>Reference</th>
<th>Last modified</th>
<th>Actions</th>
</tr>
{% for record in records %}
<tr>
<td><a href="{{ path('app_bibliography', {'id' : record.id}) }}">{{ record.id }}</a></td>
<td><a href="{{ path('app_bibliography', {'id' : record.id}) }}">{{ record.citation }}</a></td>
<td>{{ record.status }}</td>
<td>{{ record.owner }}</td>
<td style="max-width: 350px;">{{ record.reference }}</td>
<td>
{{ record.modifiedAt.format('Y-m-d') }}<br>
{{ record.editor }} at {{ record.modifiedAt.format('H:i:s') }}
</td>
<td style="min-width: 120px;">
<div class="buttons">
<button class="button is-small is-link" title="Edit record">
<span class="icon">
<i class="fa fa-edit"></i>
</span>
</button>
<button class="button is-small is-danger" title="Delete record">
<span class="icon">
<i class="fa fa-trash"></i>
</span>
</button>
</div>
</td>
</tr>
{% endfor %}
</table>
</div>
<script type="text/javascript" defer></script>
<script type="text/javascript" defer>
const delBtns = document.querySelectorAll('.delete');
for (let btn of delBtns) {
btn.addEventListener('click', function () {
this.parentElement.classList.add('is-hidden');
})
}
</script>
{% endblock %}

View File

@ -0,0 +1,89 @@
{% extends 'data_entry.html.twig' %}
{% block title %}Collection - {{ record.title }} | ArCOA{% endblock %}
{% block rightpanel %}
<div class="container" style="max-width: 60vw">
<h1 class="is-size-1 mt-0 has-text-centered">Collection</h1>
<h2 class="is-size-3 mt-3 has-text-centered">{{ record.title }}</h2>
<article class="message is-info mt-3">
<div class="message-body">
<p>
<strong>Last modified:</strong> {{ record.modifiedAt.format('Y-m-d') }}
at {{ record.modifiedAt.format('H:i:s') }}
</p>
<p><strong>Editor:</strong> {{ record.editor }}</p>
</div>
</article>
<div class="card p-5">
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<div class="columns">
<div class="column is-half"></div>
<div class="column has-text-right">
<button class="button is-link">
Edit
<span class="icon ml-2">
<i class="fa fa-edit"></i>
</span>
</button>
<button class="button is-link">
Copy
<span class="icon ml-2">
<i class="fa fa-copy"></i>
</span>
</button>
<button class="button is-danger">
Delete
<span class="icon ml-2">
<i class="fa fa-trash"></i>
</span>
</button>
</div>
</div>
{% endif %}
<div class="tabs is-boxed is-fullwidth">
<ul>
<li class="is-active">Record</li>
<li>Relations</li>
</ul>
</div>
<div class="data-tabs" id="record">
<table class="table is-fullwidth pt-4 record">
<tr><th>Record ID</th><td>{{ record.id }}</td></tr>
<tr><th>Status</th><td>{{ record.getStatus() }}</td></tr>
<tr><th>Editor(s)</th><td>{{ record.editor }}</td></tr>
<tr><th>Collection name</th><td>{{ record.title }}</td></tr>
<tr><th>Chronology</th><td>{{ record.chronology }}</td></tr>
<tr><th>Start date</th><td>{{ record.startDate }}</td></tr>
<tr><th>End date</th><td>{{ record.endDate }}</td></tr>
<tr><th>Description</th><td>{{ record.description }}</td></tr>
<tr><th>Short description</th><td>{{ record.shortDescription }}</td></tr>
<tr><th>External identifier(s)</th><td>{{ record.externalIdentifier }}</td></tr>
<tr><th>External link(s)</th><td>{{ record.link }}</td></tr>
<tr><th>Subject headings</th><td>{{ record.subjectHeadings }}</td></tr>
<tr><th>ArCOA URI</th><td>{{ record.uri }}</td></tr>
<tr><th>Editorial notes</th><td>{{ record.notes }}</td></tr>
</table>
</div>
<div class="data-tabs is-hidden" id="relations">
{% if record.bibliographies %}
<p class="p-4 has-text-bold">Bibliographies</p>
<table class="table is-fullwidth is-hoverable has-text-centered">
<tr><th>ID</th><th>Status</th><th>Citation</th><th>Editor</th><th>Reference</th></tr>
{% for biblio in record.bibliographies %}
<tr>
<td><a href="{{ path('app_bibliography', {'id' : biblio.id}) }}">{{ biblio.id }}</a></td>
<td><a href="{{ path('app_bibliography', {'id' : biblio.id}) }}">{{ biblio.citation }}</a></td>
<td>{{ biblio.status }}</td>
<td>{{ biblio.editor }}</td>
<td>{{ biblio.reference }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
</div>
</div>
<script type="text/javascript" defer></script>
{% endblock %}

View File

@ -3,9 +3,12 @@
{% block body %}
<nav class="navbar has-background-light">
<div class="navbar-start">
<div class="navbar-start ml-4">
<a href="/" class="navbar-item">
<strong>ArCOA Data Entry</strong>
<span class="icon is-clickable">
<i class="fa fa-home"></i>
</span>
<strong>ArCOA Digital Archive</strong>
</a>
</div>
{% if app.user %}
@ -56,7 +59,7 @@
</nav>
<div class="columns mb-0">
<div class="column is-one-fifth arcoa-menu mb-0">
<aside class="menu pl-4">
<aside class="menu">
{% if 'ROLE_READER' not in app.user.roles %}
<p class="menu-label has-text-white mt-3 pt-5 pl-5 is-size-6">
Vocabularies
@ -146,7 +149,7 @@
</a>
</li>
<li>
<a>
<a href="{{ path('app_vocab_object_type') }}">
<span class="icon pr-3">
<i class="fa fa-plus"></i>
</span>
@ -209,6 +212,18 @@
<i class="fa fa-angle-right"></i>
</span>
</p>
<ul class="pl-6 is-hidden has-text-white" id="records">
<li>
<a href="{{ path('app_bibliography_landing') }}">
Bibliography
</a>
</li>
<li>
<a href="">
Collection
</a>
</li>
</ul>
</aside>
</div>
<div class="column mt-6 mb-6">

View File

@ -87,7 +87,7 @@
terms.addEventListener('click', async (event) => {
const clicked = event.target;
if (clicked.classList.contains('is-danger')) {
if (clicked.getAttribute('data-id-delete')) {
const termId = clicked.getAttribute('data-id-delete');
const data = new FormData;
@ -103,7 +103,7 @@
if (res.status === 200) {
notice.innerHTML = `
<button class="delete"></button>
Term updated successfully
Term deleted successfully
`;
notice.classList.remove('is-hidden');
notice.querySelector('.delete').addEventListener('click', () => {

View File

@ -0,0 +1,167 @@
{% extends 'data_entry.html.twig' %}
{% block title %}Vocab - Functional context | ArCOA{% endblock %}
{% block rightpanel %}
<div class="container" style="max-width: 50vw">
<h1 class="is-size-1 mt-0 has-text-centered">Vocabulary</h1>
<h2 class="is-size-3 mt-4 has-text-centered">Object type</h2>
<div class="container mt-6">
<!-- For AJAX calls... TODO: not working from controller? -->
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<form method="post" action="{{ path('app_vocab_func_context_add') }}">
<label class="label">Add new term</label>
<div class="field has-addons">
<div class="control">
<input class="input" name="_term" type="text" placeholder="Add new term">
</div>
<div class="control">
<button type="submit" class="button is-link">
Add
<span class="icon ml-3">
<i class="fa fa-plus"></i>
</span>
</button>
</div>
</div>
</form>
{% endif %}
<div class="notification is-success is-hidden mt-5" id="ajax-success">
</div>
{% for message in app.flashes('notice') %}
<div class="notification is-success mt-5" id="server-success">
<button class="delete"></button>
{{ message }}
</div>
{% endfor %}
<h3 class="mt-6 mb-5 is-size-5"><strong>Terms in vocabulary</strong></h3>
<table class="table is-fullwidth" id="terms">
<tr><th>Term</th>{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}<th>Actions</th>{% endif %}</tr>
{% for term in terms %}
<tr data-row-id="{{ term.id }}">
<td>
<input class="input" type="text" value="{{ term.term }}" disabled data-term-id="{{ term.id }}" />
</td>
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<td>
<div class="buttons">
<button class="button is-primary is-hidden" data-id-save="{{ term.id }}">
Save
<span class="icon ml-2">
<i class="fa fa-save"></i>
</span>
</button>
<button class="button is-link" data-id-edit="{{ term.id }}">
Edit
<span class="icon ml-2">
<i class="fa fa-edit"></i>
</span>
</button>
<button class="button is-danger" data-id-delete="{{ term.id }}">
Delete
<span class="icon ml-2">
<i class="fa fa-trash"></i>
</span>
</button>
</div>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
</div>
<script type="text/javascript" defer>
const terms = document.querySelector('#terms');
const serverNotice = document.querySelector('#server-success');
if (serverNotice) {
serverNotice.querySelector('.delete').addEventListener('click', () => {
serverNotice.remove();
});
}
terms.addEventListener('click', async (event) => {
const clicked = event.target;
if (clicked.getAttribute('data-id-delete')) {
const termId = clicked.getAttribute('data-id-delete');
const data = new FormData;
data.append("_id", termId);
const res = await fetch("{{ path('app_vocab_func_context_del') }}", {
method: "POST",
body: data,
});
const notice = document.querySelector('#ajax-success');
if (res.status === 200) {
notice.innerHTML = `
<button class="delete"></button>
Term deleted successfully
`;
notice.classList.remove('is-hidden');
notice.querySelector('.delete').addEventListener('click', () => {
notice.classList.add('is-hidden');
});
const row = document.querySelector(`tr[data-row-id="${termId}"]`);
row.remove();
}
}
if (clicked.getAttribute('data-id-edit')) {
const termId = clicked.getAttribute('data-id-edit');
const saveBtn = document.querySelector(`button[data-id-save="${termId}"]`);
const input = document.querySelector(`input[data-term-id="${termId}"]`);
input.disabled = input.disabled ? false : true;
saveBtn.classList.toggle('is-hidden');
clicked.classList.toggle('is-link');
if (!clicked.classList.contains('is-link')) {
clicked.innerHTML = `
Cancel
<span class="icon ml-2">
<i class="fa fa-times"></i>
</span>
`;
} else {
clicked.innerHTML = `
Edit
<span class="icon ml-2">
<i class="fa fa-edit"></i>
</span>
`;
}
const data = new FormData;
data.append("_id", termId);
saveBtn.addEventListener('click', async () => {
data.append("_new_term", input.value);
const res = await fetch("{{ path('app_vocab_func_context_upd') }}", {
method: "POST",
body: data,
});
const notice = document.querySelector('#ajax-success');
if (res.status === 200) {
notice.innerHTML = `
<button class="delete"></button>
Term updated successfully
`;
notice.classList.remove('is-hidden');
notice.querySelector('.delete').addEventListener('click', () => {
notice.classList.add('is-hidden');
});
}
});
}
});
</script>
{% endblock %}