Fakultas Ilmu Komputer UI

Commit 87d2200f authored by Nabila Febri Viola's avatar Nabila Febri Viola
Browse files

Merge branch 'PBI-10-riwayat-acara-donor' into 'staging'

Pbi 10 riwayat acara donor

See merge request !115
parents 6dd73e98 fd5c32e4
Pipeline #48948 passed with stages
in 3 minutes and 51 seconds
......@@ -33,7 +33,7 @@ download_acara_donor.short_description = 'Download Acara Donor as Excel Workbook
class AcaraDonorAdmin(admin.ModelAdmin):
list_filter = ('status', AcaraDonorFilter, 'kecamatan', 'kategori')
search_fields = ('waktu_mulai',)
readonly_fields = ('nomor',
readonly_fields = ('id',
'user',
'nama_institusi',
'alamat_institusi',
......
......@@ -14,7 +14,6 @@ class AcaraDonorFactory(factory.DjangoModelFactory):
class Meta:
model = AcaraDonor
nomor = factory.Faker('uuid4')
user = factory.SubFactory(UserFactory)
status = factory.Faker('boolean', chance_of_getting_true=66)
......
# Generated by Django 3.0.5 on 2020-06-01 19:27
# Generated by Django 3.0.5 on 2020-06-02 00:45
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
......@@ -19,7 +18,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='AcaraDonor',
fields=[
('nomor', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.BooleanField(null=True)),
('nama_institusi', models.CharField(max_length=95)),
('alamat_institusi', models.CharField(max_length=140)),
......
......@@ -3,11 +3,9 @@ from django.core.validators import MinValueValidator
from django.utils.timezone import localtime
from main.models import User
from donor.models import JadwalDonor
import uuid
class AcaraDonor(models.Model):
nomor = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(null=True)
......
......@@ -25,5 +25,5 @@ class AcaraDonorSerializer(serializers.ModelSerializer):
class Meta:
model = AcaraDonor
exclude = ["user", "nomor", "status"]
exclude = ["user", ]
extra_kwargs = {'foto_lokasi': {'required': False}}
......@@ -103,31 +103,82 @@ class AcaraDonorCreateViewTest(APITestCase):
def setUp(self):
self.user = UserFactory()
self.user.save()
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' +
str(AccessToken.for_user(self.user)))
base_time = timezone.localtime(datetime.datetime(
2020, 6, 12, 9, 18, tzinfo=datetime.timezone.utc))
self.data = {"alamat_institusi": "Pacilkom",
"alamat_lokasi_donor": "Sekre Pacil",
"email_kantor": "pacil@cs.ui.ac.id",
"email_koor": "pacilia@gmail.com",
"foto_lokasi": "",
"kategori": "Terbuka",
"kecamatan": "Beji",
"keterangan": "",
"nama_institusi": "Pacilkom",
"nama_koor": "dr. Pacilia",
"no_telp_kantor": "08165342342",
"no_telp_koor": "08167021743",
"perkiraan_jumlah_donor": 455,
"waktu_berakhir": base_time
+ timezone.timedelta(hours=7),
"waktu_mulai": base_time
+ timezone.timedelta(hours=5)}
def test_create_acara_donor_with_post_request(self, mock_timezone):
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' +
str(AccessToken.for_user(self.user)))
response = self.client.post('/acara-donor/pengajuan/',
{"alamat_institusi": "Pacilkom",
"alamat_lokasi_donor": "Sekre Pacil",
"email_kantor": "pacil@cs.ui.ac.id",
"email_koor": "pacilia@gmail.com",
"foto_lokasi": "",
"kategori": "Terbuka",
"kecamatan": "Beji",
"keterangan": "",
"nama_institusi": "Pacilkom",
"nama_koor": "dr. Pacilia",
"no_telp_kantor": "08165342342",
"no_telp_koor": "08167021743",
"perkiraan_jumlah_donor": 455,
"waktu_berakhir": timezone.now()
+ timezone.timedelta(hours=7),
"waktu_mulai": timezone.now()
+ timezone.timedelta(hours=5)},
self.data,
format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_create_acara_donor_unauthorized(self, mock_timezone):
response = self.client.post('/acara-donor/pengajuan/',
self.data,
format='multipart')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class RiwayatAcaraDonorCreateViewTest(APITestCase):
""" Test module for Riwayat Acara Donor API"""
def setUp(self):
self.user = UserFactory()
self.user.save()
self.acara_donor = AcaraDonorFactory(
user=self.user,
waktu_mulai=timezone.now() + timezone.timedelta(days=1))
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' +
str(AccessToken.for_user(self.user)))
def test_get_riwayat_acara_donor_unauthorized(self):
self.client.credentials()
response = self.client.get('/acara-donor/pengajuan/riwayat/')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_pagination_is_three(self):
for _ in range(5):
AcaraDonorFactory(user=self.user)
response = self.client.get('/acara-donor/pengajuan/riwayat/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['count'] == 6)
self.assertTrue(len(response.data['results']) == 3)
def test_list_all_acara_donor(self):
for _ in range(4):
AcaraDonorFactory(user=self.user)
# Get second page and confirm it has exactly remaining 2 items
response = self.client.get('/acara-donor/pengajuan/riwayat/?page=2')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(response.data['count'] == 5)
self.assertTrue(len(response.data['results']) == 2)
def test_page_not_found(self):
for _ in range(3):
AcaraDonorFactory(user=self.user)
# Get third page when it only has 4 items
response = self.client.get('/acara-donor/pengajuan/riwayat/?page=3')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@patch('acara_donor.filters.now', new=lambda:
datetime.datetime(2020, 5, 10, 9, 0, tzinfo=datetime.timezone.utc))
......
from django.urls import path
from acara_donor.views import AcaraDonorView
from acara_donor.views import AcaraDonorView, RiwayatAcaraDonorView
urlpatterns = [
path('pengajuan/', AcaraDonorView.as_view(), name="acara-donor")
path('pengajuan/', AcaraDonorView.as_view(), name="acara-donor"),
path('pengajuan/riwayat/', RiwayatAcaraDonorView.as_view(),
name="riwayat-acara-donor")
]
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from acara_donor.models import AcaraDonor
from acara_donor.serializers import AcaraDonorSerializer
from main.paginations import ExtraSmallResultsSetPagination
class AcaraDonorView(generics.CreateAPIView):
......@@ -9,3 +11,13 @@ class AcaraDonorView(generics.CreateAPIView):
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class RiwayatAcaraDonorView(generics.ListAPIView):
serializer_class = AcaraDonorSerializer
permission_classes = [IsAuthenticated]
pagination_class = ExtraSmallResultsSetPagination
def get_queryset(self):
return AcaraDonor.objects.filter(
user=self.request.user).order_by('-waktu_mulai')
......@@ -108,6 +108,9 @@ export const getAgendaDonor = shouldReturnDefault =>
? Promise.resolve({ data: [] })
: axios.get(`/donor/jadwal/agenda/`)
export const getRiwayatAcaraDonor = page =>
axios.get(`/acara-donor/pengajuan/riwayat/${!!page ? "?page=" + page : ""}`)
export const getRiwayatDonor = page =>
axios.get(`/donor/jadwal/riwayat/${!!page ? "?page=" + page : ""}`)
......
import moment from "moment"
import { idGeneratorFactory } from "../utils/number"
const idGenerator = idGeneratorFactory(1)
export const acaraDonorFactory = override => ({
id: idGenerator(),
status: "false",
alamat_institusi: "Pacilkom",
alamat_lokasi_donor: "Sekre Pacil",
email_kantor: "pacil@cs.ui.ac.id",
email_koor: "pacilia@gmail.com",
kategori: "Terbuka",
kecamatan: "Beji",
keterangan: "",
nama_institusi: "Pacilkom",
nama_koor: "dr. Pacilia",
no_telp_kantor: "08165342342",
no_telp_koor: "08167021743",
perkiraan_jumlah_donor: 455,
waktu_berakhir: moment()
.add(1, "hours")
.format(),
waktu_mulai: moment()
.add(2, "hours")
.format(),
...override,
})
......@@ -5,6 +5,7 @@ import { useQuery } from "react-query"
import {
getAgendaDonor,
getFormulirDaftarDonor,
getRiwayatAcaraDonor,
getRiwayatDonor,
getUserProfile,
} from "../api"
......@@ -34,6 +35,29 @@ const Profile = () => {
? dataAgenda.data[0]
: null
const [pageRiwayatAcaraDonor, setPageRiwayatAcaraDonor] = useState(1)
const {
status: statusRiwayatAcaraDonor,
data: dataRiwayatAcaraDonor,
refetch: refetchRiwayatAcaraDonor,
isFetching: isFetchingRiwayatAcaraDonor,
} = useQuery(
["riwayat-acara-donor", pageRiwayatAcaraDonor],
(queryKey, page) => getRiwayatAcaraDonor(page)
)
const paginationItemCountRiwayatAcaraDonor = 5
const riwayatAcaraDonorCountPerPage = 3
const pageCountRiwayatAcaraDonor = dataRiwayatAcaraDonor
? Math.ceil(
dataRiwayatAcaraDonor.data.count / riwayatAcaraDonorCountPerPage
)
: 0
const paginationRiwayatAcaraDonor = paginationArray(
pageRiwayatAcaraDonor,
pageCountRiwayatAcaraDonor,
paginationItemCountRiwayatAcaraDonor
)
const [pageRiwayatDonor, setPageRiwayatDonor] = useState(1)
const {
status: statusRiwayatDonor,
......@@ -182,7 +206,120 @@ const Profile = () => {
<div className="row justify-content-center">
<div className="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-4 my-3">
<div className="card profile-card border-light shadow bg-white rounded mt-2 card">
<div className="card-body fluid"></div>
<div className="card-body fluid d-flex flex-column">
<h5 className="text-center text-red mb-3">
<b>Riwayat Acara Donor Darah</b>
</h5>
{isFetchingRiwayatAcaraDonor ? (
<Spinner />
) : statusRiwayatAcaraDonor === "error" ? (
<ErrorRetry retry={refetchRiwayatAcaraDonor} />
) : (
<div className="container flex-grow-1 d-flex flex-column">
{dataRiwayatAcaraDonor.data.count === 0 ? (
<div className="text-center">
Belum ada riwayat acara donor.
</div>
) : (
<>
<div className="flex-grow-1">
<table className="table table-riwayat-donor table-striped">
<thead>
<tr>
<th>Alamat</th>
<th>Waktu</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{dataRiwayatAcaraDonor.data.results.map(
riwayatAcaraDonor => {
return (
<tr key={riwayatAcaraDonor.id}>
<td>
{riwayatAcaraDonor.alamat_lokasi_donor}
</td>
<td>
{moment(
riwayatAcaraDonor.waktu_mulai
).format("D MMMM YYYY, (HH:mm")}
{moment(
riwayatAcaraDonor.waktu_berakhir
).format(" - HH:mm)")}
</td>
<td>
{riwayatAcaraDonor.status ? (
<p>Diterima</p>
) : (
<p>Ditolak</p>
)}
</td>
</tr>
)
}
)}
</tbody>
</table>
</div>
<ul className="pagination d-flex justify-content-center">
<li
className={`page-item ${
pageRiwayatAcaraDonor === 1 ? "disabled" : ""
}`}
onClick={() =>
pageRiwayatAcaraDonor > 1 &&
setPageRiwayatAcaraDonor(curPage => curPage - 1)
}
>
<span className="page-link">
«
<span className="sr-only">
Halaman sebelumnya riwayat acara donor
</span>
</span>
</li>
{paginationRiwayatAcaraDonor.map(val => (
<li
className={`page-item ${
pageRiwayatAcaraDonor === val ? "active" : ""
}`}
onClick={() => setPageRiwayatAcaraDonor(val)}
key={val}
>
<span className="page-link">
{val}
<span className="sr-only">
{`Halaman ${val} riwayat acara donor`}
</span>
</span>
</li>
))}
<li
className={`page-item ${
pageRiwayatAcaraDonor ===
pageCountRiwayatAcaraDonor
? "disabled"
: ""
}`}
onClick={() =>
pageRiwayatAcaraDonor <
pageCountRiwayatAcaraDonor &&
setPageRiwayatAcaraDonor(curPage => curPage + 1)
}
>
<span className="page-link">
»
<span className="sr-only">
Halaman selanjutnya riwayat acara donor
</span>
</span>
</li>
</ul>
</>
)}
</div>
)}
</div>
</div>
</div>
<div className="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-4 my-3">
......
......@@ -2,15 +2,18 @@ import { fireEvent, screen, waitFor } from "@testing-library/react"
import React from "react"
import {
getAgendaDonor,
getRiwayatAcaraDonor,
getRiwayatDonor,
getUserProfile,
getFormulirDaftarDonor,
} from "../api"
import { renderAuthenticated } from "../utils/test-util"
import { acaraDonorFactory } from "./acara-donor.factory"
import { jadwalDonorFactory } from "./jadwal-donor.factory"
import ProfilePage from "./profile"
getAgendaDonor.mockResolvedValue({ data: [] })
getUserProfile.mockResolvedValue({ data: { profile: {} } })
getRiwayatAcaraDonor.mockResolvedValue({ data: { count: 0, results: [] } })
getRiwayatDonor.mockResolvedValue({ data: { count: 0, results: [] } })
describe(`Kartu Donor`, () => {
......@@ -267,3 +270,123 @@ describe("Riwayat Donor", () => {
)
})
})
describe("Riwayat Acara Donor", () => {
it("shows descriptive message when there is no riwayat acara donor", async () => {
getRiwayatAcaraDonor.mockResolvedValueOnce({
data: {
count: 0,
results: [],
},
})
renderAuthenticated(<ProfilePage />)
expect(
await screen.findByText(/Belum ada riwayat acara donor/i)
).toBeInTheDocument()
await waitFor(() =>
expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
)
})
it("shows riwayat acara donor on success", async () => {
getRiwayatAcaraDonor.mockResolvedValue({
data: {
count: 1,
results: [
{
id: 1,
status: false,
alamat_institusi: "Pacilkom",
alamat_lokasi_donor: "Sekre Pacil",
email_kantor: "pacil@cs.ui.ac.id",
email_koor: "pacilia@gmail.com",
kategori: "Terbuka",
kecamatan: "Beji",
keterangan: "",
nama_institusi: "Pacilkom",
nama_koor: "dr. Pacilia",
no_telp_kantor: "08165342342",
no_telp_koor: "08167021743",
perkiraan_jumlah_donor: 455,
waktu_berakhir: "2020-06-02T12:00:00+07:00",
waktu_mulai: "2020-06-02T10:00:00+07:00",
},
],
},
})
renderAuthenticated(<ProfilePage />)
await screen.findByText("Alamat")
expect(screen.getByText("Sekre Pacil"))
expect(screen.getByText("Ditolak"))
await waitFor(() =>
expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
)
})
it("has clickable pagination", async () => {
const acara_donor1 = acaraDonorFactory({
alamat_lokasi_donor: "Yuli Pacil",
})
const acara_donor4 = acaraDonorFactory({
alamat_lokasi_donor: "Belyos Pacil",
})
getRiwayatAcaraDonor
.mockResolvedValueOnce({
data: {
count: 4,
results: [acara_donor1, acaraDonorFactory(), acaraDonorFactory()],
},
})
.mockResolvedValueOnce({
data: {
count: 4,
results: [acara_donor4],
},
})
.mockResolvedValueOnce({
data: {
count: 4,
results: [acara_donor1, acaraDonorFactory(), acaraDonorFactory()],
},
})
.mockResolvedValueOnce({
data: {
count: 4,
results: [acara_donor4],
},
})
renderAuthenticated(<ProfilePage />)
expect(await screen.findByText("Yuli Pacil")).toBeInTheDocument()
fireEvent.click(screen.getByText("Halaman 2 riwayat acara donor"))
expect(await screen.findByText("Belyos Pacil")).toBeInTheDocument()
fireEvent.click(screen.getByText("Halaman sebelumnya riwayat acara donor"))
expect(await screen.findByText("Yuli Pacil")).toBeInTheDocument()
fireEvent.click(screen.getByText("Halaman selanjutnya riwayat acara donor"))
expect(await screen.findByText("Belyos Pacil")).toBeInTheDocument()
await waitFor(() =>
expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
)
})
it("shows retry button on error and can be retried", async () => {
getRiwayatAcaraDonor
.mockRejectedValueOnce(new Error("Network error"))
.mockResolvedValueOnce({
data: {
count: 1,
results: [
acaraDonorFactory({
alamat_lokasi_donor: "Yuli Pacil",
}),
],
},
})
renderAuthenticated(<ProfilePage />)
expect(await screen.findByText(/Coba lagi/i)).toBeInTheDocument()
fireEvent.click(screen.getByText(/Coba lagi/i))
expect(await screen.findByText("Yuli Pacil")).toBeInTheDocument()
await waitFor(() =>
expect(screen.queryByText("Loading...")).not.toBeInTheDocument()
)
})
})
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment