[];
12 | for (var result in results) {
13 | var distribution = Distribution.fromJson(result);
14 | distributions.add(distribution);
15 | }
16 | return DistributionResponse(json['count'] as int, distributions);
17 | }
18 | }
19 |
20 | class Distribution {
21 | final int id;
22 | final Hospital hospital;
23 | final String sender;
24 | final String receiver;
25 |
26 | Distribution(this.id, this.hospital, this.sender, this.receiver);
27 |
28 | factory Distribution.fromJson(dynamic json) {
29 | return Distribution(json['id'] as int, Hospital.fromJson(json['hospital']),
30 | json['sender'] as String, json['receiver'] as String);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/reducers/districtsReducer.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../actions/districts'
2 |
3 | const initialState = {
4 | fetching: false,
5 | count: 0,
6 | results: [],
7 | single: {}
8 | };
9 |
10 | export default function districts(state = initialState, action) {
11 | switch (action.type) {
12 | case Actions.FETCH_DISTRICTS:
13 | return {
14 | ...state,
15 | fetching: true
16 | };
17 |
18 | case Actions.SUCCESS_FETCH_DISTRICTS:
19 | return {
20 | ...state,
21 | fetching: false,
22 | count: action.data.length,
23 | results: action.data
24 | };
25 |
26 | case Actions.FAILURE_FETCH_DISTRICTS:
27 | return {
28 | ...state,
29 | fetching: false
30 | };
31 |
32 | case Actions.FETCH_DISTRICT:
33 | return {
34 | ...state,
35 | fetching: true
36 | };
37 |
38 | case Actions.SUCCESS_FETCH_DISTRICT:
39 | return {
40 | ...state,
41 | fetching: false,
42 | single: action.data
43 | };
44 |
45 | case Actions.FAILURE_FETCH_DISTRICT:
46 | return {
47 | ...state,
48 | fetching: false
49 | };
50 |
51 | default:
52 | return state;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/reducers/hospitalsReducer.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../actions/hospitals'
2 |
3 | const initialState = {
4 | fetching: false,
5 | count: 0,
6 | results: [],
7 | single: {}
8 | };
9 |
10 | export default function hospitals(state = initialState, action) {
11 | switch (action.type) {
12 | case Actions.FETCH_HOSPITALS:
13 | return {
14 | ...state,
15 | fetching: true
16 | };
17 |
18 | case Actions.SUCCESS_FETCH_HOSPITALS:
19 | return {
20 | ...state,
21 | fetching: false,
22 | count: action.data.count,
23 | results: action.data
24 | };
25 |
26 | case Actions.FAILURE_FETCH_HOSPITALS:
27 | return {
28 | ...state,
29 | fetching: false
30 | };
31 |
32 | case Actions.FETCH_HOSPITAL:
33 | return {
34 | ...state,
35 | fetching: true
36 | };
37 |
38 | case Actions.SUCCESS_FETCH_HOSPITAL:
39 | return {
40 | ...state,
41 | fetching: false,
42 | single: action.data
43 | };
44 |
45 | case Actions.FAILURE_FETCH_HOSPITAL:
46 | return {
47 | ...state,
48 | fetching: false
49 | };
50 |
51 | default:
52 | return state;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0039_auto_20200418_1754.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-04-18 17:54
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('distributor', '0038_add_hospital_locations'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='donationdetail',
15 | name='price_per_piece',
16 | ),
17 | migrations.AddField(
18 | model_name='donationdetail',
19 | name='total_cost',
20 | field=models.DecimalField(decimal_places=2, default=0.0, help_text='Цена в сомах', max_digits=20, verbose_name='Общая стоимость'),
21 | ),
22 | migrations.AlterField(
23 | model_name='donation',
24 | name='donator_type',
25 | field=models.CharField(choices=[('ORGANIZATION', 'ORGANIZATION'), ('PERSONAL', 'PERSONAL'), ('DONOR', 'DONOR'), ('GOVERNMENT', 'GOVERNMENT')], default='ORGANIZATION', help_text='Выберите тип пожертвования', max_length=12, verbose_name='Тип пожертвования'),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/frontend/components/layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Head from 'next/head';
3 | import Header from './Header';
4 | import { initGA, logPageView } from '../../utils/ga'
5 |
6 | class Layout extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | componentDidMount () {
13 | if (!window.GA_INITIALIZED) {
14 | initGA()
15 | window.GA_INITIALIZED = true
16 | }
17 | logPageView()
18 | }
19 |
20 | updateFilterValues(regionId, districtId, localityId) {
21 | this.props.onFilterValuesChange(regionId, districtId, localityId);
22 | }
23 |
24 | render() {
25 | const {children} = this.props;
26 |
27 | return (
28 |
29 |
30 |
{'Тирек'}
31 |
32 |
33 |
36 | );
37 | }
38 | }
39 |
40 | export default Layout;
41 |
--------------------------------------------------------------------------------
/frontend/actions/creators/contacts.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../contacts';
2 | import axios from 'axios/index';
3 | import getConfig from 'next/config';
4 |
5 | const {publicRuntimeConfig} = getConfig();
6 |
7 | export const fetchContacts = () => async dispatch => {
8 | dispatch({type: Actions.FETCH_CONTACTS});
9 | try {
10 | const {data} = await axios.get(`${publicRuntimeConfig.apiUrl}/contact-info/`);
11 | return Promise.resolve(dispatch({type: Actions.SUCCESS_FETCH_CONTACTS, data}));
12 | } catch (err) {
13 | return Promise.resolve(dispatch({type: Actions.FAILURE_FETCH_CONTACTS}));
14 | }
15 | };
16 |
17 | export const sendMessage = (_data) => async dispatch => {
18 | dispatch({type: Actions.SEND_MESSAGE});
19 | try {
20 | await axios.post(`${publicRuntimeConfig.apiUrl}/contact-messages/`, _data);
21 | return Promise.resolve(dispatch({type: Actions.SUCCESS_SEND_MESSAGE}));
22 | } catch (err) {
23 | return Promise.resolve(dispatch({type: Actions.FAILURE_SEND_MESSAGE}));
24 | }
25 | };
26 |
27 | export const init = () => async dispatch => {
28 | dispatch({type: Actions.INIT_CONTACT_FORM});
29 | };
30 |
--------------------------------------------------------------------------------
/mobile/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties
5 | // are correct.
6 |
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_test/flutter_test.dart';
9 |
10 | import 'package:tirek_mobile/main.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(new TirekApplication());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0002_auto_20200325_1805.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-25 18:05
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0001_initial'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Measure',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=200, unique=True, verbose_name='Name (liters, kg, etc.)')),
19 | ],
20 | ),
21 | migrations.RemoveField(
22 | model_name='need',
23 | name='type',
24 | ),
25 | migrations.DeleteModel(
26 | name='NeedType',
27 | ),
28 | migrations.AddField(
29 | model_name='need',
30 | name='measure',
31 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='distributor.Measure', verbose_name='Measure (liters, kg, etc.)'),
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0009_auto_20200326_1932.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-26 19:32
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0008_auto_20200325_2328'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='hospital',
16 | name='phone_number',
17 | ),
18 | migrations.AddField(
19 | model_name='hospital',
20 | name='address',
21 | field=models.CharField(max_length=500, null=True, verbose_name='Address'),
22 | ),
23 | migrations.CreateModel(
24 | name='HospitalPhoneNumber',
25 | fields=[
26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
27 | ('value', models.CharField(max_length=30, verbose_name='Phone Number')),
28 | ('hospital', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.Hospital', verbose_name='Hospital')),
29 | ],
30 | ),
31 | ]
32 |
--------------------------------------------------------------------------------
/frontend/styles/_reset.scss:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | /* HTML5 display-role reset for older browsers */
22 | article, aside, details, figcaption, figure,
23 | footer, header, hgroup, menu, nav, section {
24 | display: block;
25 | }
26 | body {
27 | line-height: 1;
28 | font-family: 'Roboto', sans-serif;
29 | }
30 | ol, ul {
31 | list-style: none;
32 | }
33 | blockquote, q {
34 | quotes: none;
35 | }
36 | blockquote:before, blockquote:after,
37 | q:before, q:after {
38 | content: '';
39 | content: none;
40 | }
41 | table {
42 | border-collapse: collapse;
43 | border-spacing: 0;
44 | }
--------------------------------------------------------------------------------
/backend/distributor/migrations/0035_import_hospitals_addresses.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import os
3 |
4 | from django.db import migrations
5 |
6 |
7 | def clean_row(row):
8 | for index, value in enumerate(row):
9 | if value:
10 | row[index] = value.strip()
11 | return row
12 |
13 |
14 | def import_hospitals_addresses(apps, schema_editor):
15 | Hospital = apps.get_model('distributor', 'Hospital')
16 |
17 | file_path = os.path.dirname(os.path.realpath(__file__)) + '/files/20200402-hospital-addresses.csv'
18 | with open(file_path, newline='') as file:
19 | csv_reader = csv.reader(file, delimiter=',', quotechar="'")
20 | next(csv_reader)
21 | for row in csv_reader:
22 | code, _, _, address = clean_row(row)
23 | if address and code:
24 | hospital = Hospital.objects.filter(code=code).first()
25 | if hospital:
26 | hospital.address = address
27 | hospital.save()
28 |
29 |
30 | class Migration(migrations.Migration):
31 | dependencies = [
32 | ('distributor', '0034_auto_20200402_1233'),
33 | ]
34 |
35 | operations = [
36 | migrations.RunPython(import_hospitals_addresses),
37 | ]
38 |
--------------------------------------------------------------------------------
/frontend/actions/creators/districts.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../districts';
2 | import axios from 'axios/index';
3 | import getConfig from 'next/config';
4 |
5 | const {publicRuntimeConfig} = getConfig();
6 |
7 | export const fetchDistricts = (regionId) => async dispatch => {
8 | const params = {};
9 | if (regionId) {
10 | params['region'] = regionId;
11 | }
12 | dispatch({type: Actions.FETCH_DISTRICTS});
13 | try {
14 | const {data} = await axios.get(`${publicRuntimeConfig.apiUrl}/districts/`, {
15 | params: params
16 | });
17 | return Promise.resolve(dispatch({type: Actions.SUCCESS_FETCH_DISTRICTS, data}));
18 | } catch (err) {
19 | return Promise.resolve(dispatch({type: Actions.FAILURE_FETCH_DISTRICTS}));
20 | }
21 | };
22 |
23 | export const fetchDistrict = (districtId) => async dispatch => {
24 | dispatch({type: Actions.FETCH_DISTRICT});
25 | try {
26 | const {data} = await axios.get(`${publicRuntimeConfig.apiUrl}/districts/${districtId}/`);
27 | return Promise.resolve(dispatch({type: Actions.SUCCESS_FETCH_DISTRICT, data}));
28 | } catch (err) {
29 | return Promise.resolve(dispatch({type: Actions.FAILURE_FETCH_DISTRICT}));
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/frontend/actions/creators/hospitals.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../hospitals';
2 | import axios from 'axios/index';
3 | import getConfig from 'next/config';
4 | const { publicRuntimeConfig } = getConfig();
5 |
6 | export const fetchHospitals = (regionId, districtId, localityId, search) => async dispatch => {
7 | dispatch({ type: Actions.FETCH_HOSPITALS });
8 | try {
9 | const { data } = await axios.get(`${publicRuntimeConfig.apiUrl}/all_hospitals/`, {
10 | params: {
11 | search_region_id: regionId,
12 | search_district_id: districtId,
13 | search_locality_id: localityId,
14 | search
15 | }
16 | });
17 | return Promise.resolve(dispatch({ type: Actions.SUCCESS_FETCH_HOSPITALS, data }));
18 | } catch (err) {
19 | return Promise.resolve(dispatch({ type: Actions.FAILURE_FETCH_HOSPITALS }));
20 | }
21 | };
22 | export const fetchHospital = (hospitalId) => async dispatch => {
23 | dispatch({ type: Actions.FETCH_HOSPITAL });
24 | try {
25 | const { data } = await axios.get(`${publicRuntimeConfig.apiUrl}/hospitals/${hospitalId}/`);
26 | return Promise.resolve(dispatch({ type: Actions.SUCCESS_FETCH_HOSPITAL, data }));
27 | } catch (err) {
28 | return Promise.resolve(dispatch({ type: Actions.FAILURE_FETCH_HOSPITAL }));
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/backend/templates/admin/base_site.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/base.html" %}
2 | {% load i18n %}
3 | {% load tags %}
4 | {% load static %}
5 |
6 | {% block extrastyle %}
7 | {{ block.super }}
8 |
9 | {% endblock %}
10 |
11 | {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
12 | {% block extrahead %}
13 |
28 | {% endblock %}
29 |
30 | {% block branding %}
31 |
32 |
33 | {% trans "version" %} {% current_application_version %}
34 |
35 | {% endblock %}
36 |
37 | {% block nav-global %}{% endblock %}
--------------------------------------------------------------------------------
/mobile/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "503370235857",
4 | "firebase_url": "https://flutter-login-demo-5f414.firebaseio.com",
5 | "project_id": "flutter-login-demo-5f414",
6 | "storage_bucket": "flutter-login-demo-5f414.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:503370235857:android:253e1da980d40be4",
12 | "android_client_info": {
13 | "package_name": "com.example.flutterlogindemo"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "503370235857-3cuorpuf59haleofguql991tiv51db1b.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyDIOybdmyaxGXA7Mp7sx7y905lBMTIQfCQ"
25 | }
26 | ],
27 | "services": {
28 | "analytics_service": {
29 | "status": 1
30 | },
31 | "appinvite_service": {
32 | "status": 1,
33 | "other_platform_oauth_client": []
34 | },
35 | "ads_service": {
36 | "status": 2
37 | }
38 | }
39 | }
40 | ],
41 | "configuration_version": "1"
42 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 |
4 | backend:
5 | build:
6 | context: backend
7 | container_name: backend
8 | ports:
9 | - 8000:8000
10 | depends_on:
11 | - distributor-db
12 | - redis
13 | links:
14 | - distributor-db
15 | - redis
16 | env_file:
17 | - ./backend/.env
18 | environment:
19 | - DISTRIBUTOR_DB_HOST=distributor-db
20 | - DISTRIBUTOR_DB_NAME=distributor
21 | - DISTRIBUTOR_DB_USER=master
22 | - DISTRIBUTOR_DB_PASSWORD=123456
23 | - DISTRIBUTOR_DB_PORT=5432
24 | - REDIS_HOST=redis
25 | - REDIS_PORT=6379
26 |
27 | redis:
28 | image: redis:alpine
29 | container_name: redis
30 | restart: always
31 | expose:
32 | - 6379
33 | ports:
34 | - 6379:6379
35 |
36 | distributor-db:
37 | image: mdillon/postgis:9.5
38 | container_name: distributor-db
39 | hostname: distributor-db
40 | environment:
41 | - POSTGRES_USER=master
42 | - POSTGRES_PASSWORD=123456
43 | - POSTGRES_DB=distributor
44 | ports:
45 | - 5435:5432
46 | volumes:
47 | - distributor-postgres-data:/var/lib/postgresql/data
48 |
49 | volumes:
50 | distributor-postgres-data:
51 | driver: local
52 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0019_auto_20200328_0130.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-26 19:32
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0018_auto_20200327_2329'),
11 | ]
12 |
13 | operations = [
14 |
15 |
16 | migrations.CreateModel(
17 | name='HospitalNeeds',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('reserve_amount', models.IntegerField(verbose_name='Reserve amount')),
21 | ('request_amount', models.IntegerField(verbose_name='Request amount')),
22 | ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created Date')),
23 | ('hospital', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.Hospital', verbose_name='Hospital')),
24 | ('need_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.NeedType',
25 | verbose_name='Need Type')),
26 | ],
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0053_fix_distribution_details.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-04-23 06:26
2 |
3 | from django.db import migrations, models, transaction
4 | import django.db.models.deletion
5 |
6 | def trunc_distributions(apps, schema_editor):
7 | with transaction.atomic():
8 | Distribution = apps.get_model('distributor', 'Distribution')
9 | Distribution.objects.all().delete()
10 |
11 | class Migration(migrations.Migration):
12 | atomic = False
13 |
14 | dependencies = [
15 | ('distributor', '0052_auto_20200422_2007'),
16 | ]
17 |
18 | operations = [
19 | migrations.RunPython(trunc_distributions),
20 | migrations.AddField(
21 | model_name='distribution',
22 | name='donation',
23 | field=models.ForeignKey(help_text='Выберите ранее созданное пожертвование',
24 | on_delete=django.db.models.deletion.PROTECT, related_name='donation_details',
25 | to='distributor.Donation', verbose_name='Пожертвование)'),
26 | preserve_default=False,
27 | ),
28 | migrations.RemoveField(
29 | model_name='distributiondetail',
30 | name='donation',
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/frontend/actions/creators/localities.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../localities';
2 | import axios from 'axios/index';
3 | import getConfig from 'next/config';
4 |
5 | const {publicRuntimeConfig} = getConfig();
6 |
7 | export const fetchLocalities = (districtId) => async dispatch => {
8 | dispatch({type: Actions.FETCH_LOCALITIES});
9 | try {
10 | const params = {};
11 | if (districtId) {
12 | params['district'] = districtId;
13 | }
14 | const {data} = await axios.get(`${publicRuntimeConfig.apiUrl}/localities/`, {
15 | params: params
16 | });
17 | return Promise.resolve(dispatch({type: Actions.SUCCESS_FETCH_LOCALITIES, data}));
18 | } catch (err) {
19 | return Promise.resolve(dispatch({type: Actions.FAILURE_FETCH_LOCALITIES}));
20 | }
21 | };
22 |
23 | export const fetchLocality = (localityId) => async dispatch => {
24 | dispatch({type: Actions.FETCH_LOCALITY});
25 | try {
26 | const {data} = await axios.get(`${publicRuntimeConfig.apiUrl}/localities/${localityId}/`);
27 | return Promise.resolve(dispatch({type: Actions.SUCCESS_FETCH_LOCALITY, data}));
28 | } catch (err) {
29 | return Promise.resolve(dispatch({type: Actions.FAILURE_FETCH_LOCALITY}));
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/backend/backend/urls.py:
--------------------------------------------------------------------------------
1 | """backend URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url
17 | from django.contrib import admin
18 | from django.urls import path, include
19 | from rest_framework.documentation import include_docs_urls
20 |
21 | v1 = ([
22 | path('', include('distributor.urls')),
23 | path('', include('users.urls')),
24 | ], 'v1')
25 |
26 | urlpatterns = [
27 | url(r'^jet/', include('jet.urls', 'jet')),
28 | path('admin/', admin.site.urls),
29 | path('api/v1/', include(v1)),
30 | url(r'^api-auth/', include('rest_framework.urls')),
31 | url(r'^docs/', include_docs_urls(title='API Docs', public=False)),
32 | ]
33 |
--------------------------------------------------------------------------------
/backend/DEPLOY.md:
--------------------------------------------------------------------------------
1 | ### How to deploy
2 |
3 | Copy .env.dev to .env file, then edit:
4 | ```
5 | SECRET_KEY=put something weird here
6 | DJANGO_SETTINGS_MODULE=backend.production
7 | STATIC_ROOT = '/var/www/covid-supply/static'
8 | ```
9 |
10 | Run commands:
11 | ```
12 | # copy static files
13 | python manage.py collectstatic
14 | # run migrations
15 | python manage.py migrate
16 | # restart gunicorn
17 | systemctl restart gunicorn
18 | ```
19 |
20 | Gunicorn systemd file, note the `!SET_PORT_HERE!` thingie:
21 | ```
22 | [Unit]
23 | Description=gunicorn daemon
24 | After=network.target
25 |
26 | [Service]
27 | Type=simple
28 | # the specific user that our service will run as
29 | User=www-data
30 | Group=www-data
31 | # another option for an even more restricted service is
32 | # DynamicUser=yes
33 | # see http://0pointer.net/blog/dynamic-users-with-systemd.html
34 | WorkingDirectory=/opt/covid-supply/backend
35 | ExecStart=/opt/covid-supply/backend/venv/bin/gunicorn -b 127.0.0.1:!SET_PORT_HERE! -w 3 backend.wsgi
36 | ExecReload=/bin/kill -s HUP $MAINPID
37 | KillMode=mixed
38 | TimeoutStopSec=5
39 | PrivateTmp=true
40 |
41 | [Install]
42 | WantedBy=multi-user.target
43 | ```
44 |
45 | Create log dir & file:
46 | ```
47 | mkdir -p /var/log/django
48 | touch /var/log/django/error.log
49 | chown -R www-data /var/log/django
50 | ```
51 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/zeit/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on ZEIT Now
27 |
28 | The easiest way to deploy your Next.js app is to use the [ZEIT Now Platform](https://zeit.co/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0012_statistic.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-27 18:57
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0011_auto_20200326_2123'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Statistic',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=200, verbose_name='Name')),
19 | ('name_ru', models.CharField(max_length=200, null=True, verbose_name='Name')),
20 | ('name_ky', models.CharField(max_length=200, null=True, verbose_name='Name')),
21 | ('actual', models.PositiveSmallIntegerField(verbose_name='Actual')),
22 | ('capacity', models.PositiveSmallIntegerField(verbose_name='Capacity')),
23 | ('has_capacity', models.BooleanField(default=False, verbose_name='Has Capacity?')),
24 | ('hospital', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='statistics', to='distributor.Hospital', verbose_name='Hospital')),
25 | ],
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0021_page.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-28 21:50
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('distributor', '0020_auto_20200328_1131'),
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='Page',
15 | fields=[
16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17 | ('name', models.CharField(max_length=200, verbose_name='Name')),
18 | ('name_ru', models.CharField(max_length=200, null=True, verbose_name='Name')),
19 | ('name_ky', models.CharField(max_length=200, null=True, verbose_name='Name')),
20 | ('url', models.CharField(max_length=200, verbose_name='Url')),
21 | ('content', models.TextField(max_length=1000, verbose_name='Content')),
22 | ('content_ru', models.TextField(max_length=1000, null=True, verbose_name='Content')),
23 | ('content_ky', models.TextField(max_length=1000, null=True, verbose_name='Content')),
24 | ],
25 | options={
26 | 'verbose_name': 'Page',
27 | 'verbose_name_plural': 'Pages',
28 | },
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/frontend/reducers/localitiesReducer.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../actions/localities'
2 |
3 | const initialState = {
4 | fetching: false,
5 | count: 0,
6 | results: [],
7 | single: {}
8 | };
9 |
10 | export default function hospitals(state = initialState, action) {
11 | switch (action.type) {
12 | case Actions.FETCH_LOCALITIES:
13 | return {
14 | ...state,
15 | fetching: true
16 | };
17 |
18 | case Actions.SUCCESS_FETCH_LOCALITIES:
19 | return {
20 | ...state,
21 | fetching: false,
22 | count: action.data.length,
23 | results: action.data
24 | };
25 |
26 | case Actions.FAILURE_FETCH_LOCALITIES:
27 | return {
28 | ...state,
29 | fetching: false
30 | };
31 |
32 | case Actions.FETCH_LOCALITY:
33 | return {
34 | ...state,
35 | fetching: true
36 | };
37 |
38 | case Actions.SUCCESS_FETCH_LOCALITY:
39 | return {
40 | ...state,
41 | fetching: false,
42 | single: action.data
43 | };
44 |
45 | case Actions.FAILURE_FETCH_LOCALITY:
46 | return {
47 | ...state,
48 | fetching: false
49 | };
50 |
51 | default:
52 | return state;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/components/contacts/Info.jsx:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from "react";
2 |
3 | const Info = (props) => {
4 | const {data} = props;
5 | const whatsAppFormat = (number) => {
6 | const oldNumber = number.replace(/\s+/g, '').replace(/-/g, '')
7 | const newNumber = oldNumber.indexOf('0') == 0 ? '996'+oldNumber.substring(1) : oldNumber;
8 | return newNumber
9 | };
10 |
11 | return (
12 |
13 | Контакты
14 |
15 | {data.text_ru}:
16 |
17 |
18 | {
19 | data.phone_numbers && data.phone_numbers.map(number =>
20 | -
21 | {number.value}
22 | {number.is_whats_app && }
23 |
24 | )
25 | }
26 |
27 |
28 |
29 | Email: {
30 | data.emails && data.emails.map(email =>
31 |
32 | {email.value}
33 |
34 |
35 | )
36 | }
37 |
38 |
39 | )
40 | };
41 |
42 | export default Info
43 |
--------------------------------------------------------------------------------
/mobile/tirek_mobile_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/mobile/ios/GoogleService-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AD_UNIT_ID_FOR_BANNER_TEST
6 | ca-app-pub-3940256099942544/2934735716
7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST
8 | ca-app-pub-3940256099942544/4411468910
9 | CLIENT_ID
10 | 503370235857-sh78fhp4s2r2qtqaajb1m32pkfd0l1s9.apps.googleusercontent.com
11 | REVERSED_CLIENT_ID
12 | com.googleusercontent.apps.503370235857-sh78fhp4s2r2qtqaajb1m32pkfd0l1s9
13 | API_KEY
14 | AIzaSyDQcM5m7eR7kVZ2Rl_EQOC-eXQ_h3KYWtc
15 | GCM_SENDER_ID
16 | 503370235857
17 | PLIST_VERSION
18 | 1
19 | BUNDLE_ID
20 | com.example.flutterLoginDemo
21 | PROJECT_ID
22 | flutter-login-demo-5f414
23 | STORAGE_BUCKET
24 | flutter-login-demo-5f414.appspot.com
25 | IS_ADS_ENABLED
26 |
27 | IS_ANALYTICS_ENABLED
28 |
29 | IS_APPINVITE_ENABLED
30 |
31 | IS_GCM_ENABLED
32 |
33 | IS_SIGNIN_ENABLED
34 |
35 | GOOGLE_APP_ID
36 | 1:503370235857:ios:fae8bbf356b4bb88
37 | DATABASE_URL
38 | https://flutter-login-demo-5f414.firebaseio.com
39 |
40 |
--------------------------------------------------------------------------------
/mobile/ios/Runner/GoogleService-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AD_UNIT_ID_FOR_BANNER_TEST
6 | ca-app-pub-3940256099942544/2934735716
7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST
8 | ca-app-pub-3940256099942544/4411468910
9 | CLIENT_ID
10 | 503370235857-sh78fhp4s2r2qtqaajb1m32pkfd0l1s9.apps.googleusercontent.com
11 | REVERSED_CLIENT_ID
12 | com.googleusercontent.apps.503370235857-sh78fhp4s2r2qtqaajb1m32pkfd0l1s9
13 | API_KEY
14 | AIzaSyDQcM5m7eR7kVZ2Rl_EQOC-eXQ_h3KYWtc
15 | GCM_SENDER_ID
16 | 503370235857
17 | PLIST_VERSION
18 | 1
19 | BUNDLE_ID
20 | com.example.flutterLoginDemo
21 | PROJECT_ID
22 | flutter-login-demo-5f414
23 | STORAGE_BUCKET
24 | flutter-login-demo-5f414.appspot.com
25 | IS_ADS_ENABLED
26 |
27 | IS_ANALYTICS_ENABLED
28 |
29 | IS_APPINVITE_ENABLED
30 |
31 | IS_GCM_ENABLED
32 |
33 | IS_SIGNIN_ENABLED
34 |
35 | GOOGLE_APP_ID
36 | 1:503370235857:ios:fae8bbf356b4bb88
37 | DATABASE_URL
38 | https://flutter-login-demo-5f414.firebaseio.com
39 |
40 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0027_auto_20200330_1631.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-30 16:31
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('distributor', '0026_merge_20200329_1841'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='donationdetail',
15 | name='amount',
16 | field=models.PositiveIntegerField(help_text='Введите количество', verbose_name='Количество'),
17 | ),
18 | migrations.AlterField(
19 | model_name='donationdetail',
20 | name='price_per_piece',
21 | field=models.DecimalField(decimal_places=2, default=1, help_text='Цена в сомах', max_digits=12, verbose_name='Цена за одну единицу (KGS)'),
22 | preserve_default=False,
23 | ),
24 | migrations.AlterField(
25 | model_name='statistic',
26 | name='actual',
27 | field=models.IntegerField(verbose_name='Текущий показатель'),
28 | ),
29 | migrations.AlterField(
30 | model_name='statistic',
31 | name='capacity',
32 | field=models.IntegerField(blank=True, null=True, verbose_name='Требуемое количество'),
33 | ),
34 | migrations.AlterField(
35 | model_name='statistic',
36 | name='has_capacity',
37 | field=models.BooleanField(default=False, help_text='Отметьте галочкой чтобы показать поле', verbose_name='Показывать поле "Требуемое количество" '),
38 | ),
39 | ]
40 |
--------------------------------------------------------------------------------
/mobile/lib/helper/ApiHelper.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:http/http.dart' as http;
5 | import 'package:tirek_mobile/exception/TirekException.dart';
6 |
7 | class ApiHelper {
8 | static final String baseUrl = "https://antivirus.el.kg/api/v1/";
9 |
10 | static Future post(
11 | String path, Map headers, String body) async {
12 | try {
13 | final response =
14 | await http.post(baseUrl + path, headers: headers, body: body);
15 |
16 | return _returnResponse(response);
17 | } on SocketException {
18 | throw FetchDataException('Нет подключения к интернету');
19 | }
20 | }
21 |
22 | static Future get(String path, Map headers) async {
23 | try {
24 | final response = await http.get(baseUrl + path, headers: headers);
25 |
26 | return _returnResponse(response);
27 | } on SocketException {
28 | throw FetchDataException('Нет подключения к интернету');
29 | }
30 | }
31 |
32 | static dynamic _returnResponse(http.Response response) {
33 | switch (response.statusCode) {
34 | case 200:
35 | case 201:
36 | var responseJson = json.decode(utf8.decode(response.bodyBytes));
37 | return responseJson;
38 | case 400:
39 | throw BadRequestException(response.body.toString());
40 | case 401:
41 | case 403:
42 | throw UnauthorisedException(response.body.toString());
43 | case 500:
44 | default:
45 | throw FetchDataException('Произошла ошибка : ${response.statusCode}');
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0039_distribution.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-04-05 16:14
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0038_add_hospital_locations'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Distribution',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('sender', models.TextField(verbose_name='Кто выдал?')),
19 | ('receiver', models.TextField(verbose_name='Кто принял?')),
20 | ('date_of_distribute', models.DateField(verbose_name='Дата распределения')),
21 | ('status', models.CharField(choices=[('ready_to_sent', 'Подготовлено'), ('sent', 'Отправлено'), ('delivered', 'Доставлено')], max_length=20, verbose_name='Статус')),
22 | ('created_at', models.DateTimeField(auto_now_add=True)),
23 | ('updated_at', models.DateTimeField(auto_now=True)),
24 | ('donations', models.ManyToManyField(to='distributor.Donation', verbose_name='Пожертвования')),
25 | ('hospital', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='distributions', to='distributor.Hospital')),
26 | ],
27 | options={
28 | 'verbose_name': 'Запись для распределения',
29 | 'verbose_name_plural': 'Распределения',
30 | },
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/frontend/components/distributions/distribution.js:
--------------------------------------------------------------------------------
1 | import {Card, Table} from "react-bootstrap";
2 | import React, {Fragment} from "react";
3 |
4 | const Distribution = (props) => {
5 | const item = props.item;
6 | const details = (item.details || []).map(detail => {
7 | const key = `${item.id} ${detail.id}`
8 | return (
9 |
10 | |
11 |
12 | {detail.need_type.name}
13 |
14 | |
15 |
16 |
17 | {detail.amount} {detail.need_type.measure.name}
18 |
19 | |
20 |
21 | )
22 | });
23 |
24 | return (
25 |
26 |
27 |
28 | {item.hospital.name}
29 |
30 |
31 | Получено: {item.receiver} {item.distributed_at}
32 |
33 |
34 |
35 |
36 |
37 | | Наименование |
38 | Кол-во |
39 |
40 |
41 |
42 | {details}
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default Distribution;
--------------------------------------------------------------------------------
/backend/distributor/migrations/0008_auto_20200325_2328.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-25 23:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('distributor', '0007_auto_20200325_2322'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='donation',
15 | name='description_ky',
16 | field=models.TextField(max_length=1000, null=True, verbose_name='Donation Description'),
17 | ),
18 | migrations.AddField(
19 | model_name='donation',
20 | name='description_ru',
21 | field=models.TextField(max_length=1000, null=True, verbose_name='Donation Description'),
22 | ),
23 | migrations.AddField(
24 | model_name='donation',
25 | name='donator_name_ky',
26 | field=models.CharField(max_length=200, null=True, verbose_name='Donator Name'),
27 | ),
28 | migrations.AddField(
29 | model_name='donation',
30 | name='donator_name_ru',
31 | field=models.CharField(max_length=200, null=True, verbose_name='Donator Name'),
32 | ),
33 | migrations.AddField(
34 | model_name='hospital',
35 | name='name_ky',
36 | field=models.CharField(max_length=200, null=True, verbose_name='Name'),
37 | ),
38 | migrations.AddField(
39 | model_name='hospital',
40 | name='name_ru',
41 | field=models.CharField(max_length=200, null=True, verbose_name='Name'),
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0017_helprequest.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-27 22:48
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0016_auto_20200327_2112'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='HelpRequest',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('first_name', models.CharField(max_length=50, verbose_name='First Name')),
19 | ('last_name', models.CharField(max_length=50, verbose_name='Last Name')),
20 | ('position', models.CharField(max_length=50, verbose_name='Position')),
21 | ('hospital_name', models.CharField(max_length=250, verbose_name='Hospital Name')),
22 | ('phone_number', models.CharField(max_length=100, verbose_name='Phone Number')),
23 | ('description', models.TextField(max_length=500, verbose_name='Description')),
24 | ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created Date')),
25 | ('is_read', models.BooleanField(default=False, verbose_name='Read')),
26 | ('read_at', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Read Date')),
27 | ('locality', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='distributor.Locality', verbose_name='Locality')),
28 | ],
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/mobile/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | tirek_mobile
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/frontend/reducers/contactsReducer.js:
--------------------------------------------------------------------------------
1 | import * as Actions from '../actions/contacts'
2 | import * as status from '../constants/messageStatus'
3 |
4 | const initialState = {
5 | sending: false,
6 | status: status.INIT,
7 | fetching: false,
8 | data: {}
9 | };
10 |
11 | export default function districts(state = initialState, action) {
12 | switch (action.type) {
13 | case Actions.FETCH_CONTACTS:
14 | return {
15 | ...state,
16 | fetching: true
17 | };
18 |
19 | case Actions.SUCCESS_FETCH_CONTACTS:
20 | return {
21 | ...state,
22 | fetching: false,
23 | data: action.data
24 | };
25 |
26 | case Actions.FAILURE_FETCH_CONTACTS:
27 | return {
28 | ...state,
29 | fetching: false
30 | };
31 |
32 | case Actions.SEND_MESSAGE:
33 | return {
34 | ...state,
35 | sending: true
36 | };
37 |
38 | case Actions.SUCCESS_SEND_MESSAGE:
39 | return {
40 | ...state,
41 | sending: false,
42 | status: status.SUCCESS
43 | };
44 |
45 | case Actions.FAILURE_SEND_MESSAGE:
46 | return {
47 | ...state,
48 | sending: false,
49 | status: status.FAILURE
50 | };
51 |
52 | case Actions.INIT_CONTACT_FORM:
53 | return {
54 | ...state,
55 | status: status.INIT
56 | };
57 |
58 | default:
59 | return state;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/flutter
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=flutter
4 |
5 | ### Flutter ###
6 | # Flutter/Dart/Pub related
7 | **/doc/api/
8 | .dart_tool/
9 | .flutter-plugins
10 | .flutter-plugins-dependencies
11 | .packages
12 | .pub-cache/
13 | .pub/
14 | build/
15 |
16 | # Android related
17 | **/android/**/gradle-wrapper.jar
18 | **/android/.gradle
19 | **/android/captures/
20 | **/android/gradlew
21 | **/android/gradlew.bat
22 | **/android/local.properties
23 | **/android/**/GeneratedPluginRegistrant.java
24 |
25 | # iOS/XCode related
26 | **/ios/**/*.mode1v3
27 | **/ios/**/*.mode2v3
28 | **/ios/**/*.moved-aside
29 | **/ios/**/*.pbxuser
30 | **/ios/**/*.perspectivev3
31 | **/ios/**/*sync/
32 | **/ios/**/.sconsign.dblite
33 | **/ios/**/.tags*
34 | **/ios/**/.vagrant/
35 | **/ios/**/DerivedData/
36 | **/ios/**/Icon?
37 | **/ios/**/Pods/
38 | **/ios/**/.symlinks/
39 | **/ios/**/profile
40 | **/ios/**/xcuserdata
41 | **/ios/.generated/
42 | **/ios/Flutter/App.framework
43 | **/ios/Flutter/Flutter.framework
44 | **/ios/Flutter/Flutter.podspec
45 | **/ios/Flutter/Generated.xcconfig
46 | **/ios/Flutter/app.flx
47 | **/ios/Flutter/app.zip
48 | **/ios/Flutter/flutter_assets/
49 | **/ios/Flutter/flutter_export_environment.sh
50 | **/ios/ServiceDefinitions.json
51 | **/ios/Runner/GeneratedPluginRegistrant.*
52 |
53 | # Exceptions to above rules.
54 | !**/ios/**/default.mode1v3
55 | !**/ios/**/default.mode2v3
56 | !**/ios/**/default.pbxuser
57 | !**/ios/**/default.perspectivev3
58 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
59 |
60 | # End of https://www.toptal.com/developers/gitignore/api/flutter
--------------------------------------------------------------------------------
/.github/workflows/django.yml:
--------------------------------------------------------------------------------
1 | # source - https://hacksoft.blog/github-actions-in-action-setting-up-django-and-postgres/
2 | name: Python application
3 | on:
4 | push:
5 | paths:
6 | - 'backend/*'
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | services:
11 | distributor-db:
12 | image: mdillon/postgis:9.5
13 | env:
14 | POSTGRES_USER: master
15 | POSTGRES_PASSWORD: 123456
16 | POSTGRES_DB: distributor
17 | ports:
18 | - 5432:5432
19 | # needed because the postgres container does not provide a healthcheck
20 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
21 | redis:
22 | image: redis:alpine
23 | ports:
24 | - 6379:6379
25 | steps:
26 | - name: Checkout tha sauce
27 | uses: actions/checkout@v1
28 |
29 | - name: Set up Python 3.7
30 | uses: actions/setup-python@v1
31 | with:
32 | python-version: 3.7
33 |
34 | - name: psycopg2 prerequisites
35 | run: sudo apt-get install python-dev libpq-dev binutils libproj-dev gdal-bin
36 |
37 | - name: Install dependencies
38 | run: |
39 | python -m pip install --upgrade pip
40 | pip install -r requirements.txt
41 | working-directory: backend
42 |
43 | - name: Prepare dotenv
44 | run: cp .env.github .env
45 | working-directory: backend
46 |
47 | - name: Run migrations
48 | run: python manage.py migrate
49 | working-directory: backend
50 |
51 | - name: Run tests
52 | run: python manage.py test
53 | working-directory: backend
54 |
--------------------------------------------------------------------------------
/mobile/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0044_auto_20200420_1853.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-04-20 18:53
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0043_auto_20200405_1813'),
11 | ]
12 |
13 | operations = [
14 | migrations.RemoveField(
15 | model_name='distribution',
16 | name='donations',
17 | ),
18 | migrations.CreateModel(
19 | name='DistributionDetail',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('amount', models.PositiveIntegerField(help_text='Введите количество', verbose_name='Количество')),
23 | ('price_per_piece', models.DecimalField(decimal_places=2, help_text='Цена в сомах', max_digits=12, verbose_name='Цена за одну единицу (KGS)')),
24 | ('distribution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='distributor.Distribution')),
25 | ('donation', models.ForeignKey(help_text='Выберите ранее созданное пожертвование', on_delete=django.db.models.deletion.PROTECT, related_name='distribution_details', to='distributor.Donation', verbose_name='Пожертвование)')),
26 | ('need_type', models.ForeignKey(help_text='Выберите тип нужды', on_delete=django.db.models.deletion.PROTECT, to='distributor.NeedType', verbose_name='Тип нужды')),
27 | ],
28 | options={
29 | 'verbose_name': 'Детали распределния',
30 | 'verbose_name_plural': 'Детали распределений',
31 | },
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/mobile/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | apply plugin: 'com.android.application'
15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
16 |
17 | android {
18 | compileSdkVersion 28
19 |
20 | lintOptions {
21 | disable 'InvalidPackage'
22 | }
23 |
24 | defaultConfig {
25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
26 | applicationId "com.example.flutterlogindemo"
27 | minSdkVersion 16
28 | targetSdkVersion 28
29 | versionCode 1
30 | versionName "1.0"
31 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
32 | }
33 |
34 | buildTypes {
35 | release {
36 | // TODO: Add your own signing config for the release build.
37 | // Signing with the debug keys for now, so `flutter run --release` works.
38 | signingConfig signingConfigs.debug
39 | }
40 | }
41 | }
42 |
43 | flutter {
44 | source '../..'
45 | }
46 |
47 | dependencies {
48 | testImplementation 'junit:junit:4.12'
49 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
51 | }
52 |
53 | apply plugin: 'com.google.gms.google-services'
--------------------------------------------------------------------------------
/backend/distributor/migrations/0003_auto_20200325_1806.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-25 18:06
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ('distributor', '0002_auto_20200325_1805'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='NeedType',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=200, verbose_name='Name')),
21 | ('price_per_piece', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Price per piece (KGS)')),
22 | ('modified_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Modified Date')),
23 | ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created Date')),
24 | ('created_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='needtype_created_by', to=settings.AUTH_USER_MODEL)),
25 | ('measure', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='distributor.Measure', verbose_name='Measure (liters, kg, etc.)')),
26 | ('modified_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='needtype_modified_by', to=settings.AUTH_USER_MODEL)),
27 | ],
28 | ),
29 | migrations.DeleteModel(
30 | name='Need',
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/frontend/config/with-redux-store.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { initializeStore } from './store'
3 |
4 | const isServer = typeof window === 'undefined'
5 | const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
6 |
7 | function getOrCreateStore (initialState) {
8 | // Always make a new store if server, otherwise state is shared between requests
9 | if (isServer) {
10 | return initializeStore(initialState)
11 | }
12 |
13 | // Create store if unavailable on the client and set it on the window object
14 | if (!window[__NEXT_REDUX_STORE__]) {
15 | window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
16 | }
17 | return window[__NEXT_REDUX_STORE__]
18 | }
19 |
20 | export default App => {
21 | return class AppWithRedux extends React.Component {
22 | static async getInitialProps (appContext) {
23 | // Get or Create the store with `undefined` as initialState
24 | // This allows you to set a custom default initialState
25 | const reduxStore = getOrCreateStore()
26 |
27 | // Provide the store to getInitialProps of pages
28 | appContext.ctx.reduxStore = reduxStore
29 |
30 | let appProps = {}
31 | if (typeof App.getInitialProps === 'function') {
32 | appProps = await App.getInitialProps(appContext)
33 | }
34 |
35 | return {
36 | ...appProps,
37 | initialReduxState: reduxStore.getState()
38 | }
39 | }
40 |
41 | constructor (props) {
42 | super(props)
43 | this.reduxStore = getOrCreateStore(props.initialReduxState)
44 | }
45 |
46 | render () {
47 | return
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/mobile/lib/services/NeedsRequestService.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:tirek_mobile/helper/ApiHelper.dart';
3 |
4 | import 'package:tirek_mobile/models/response/NeedsRequestResponse.dart';
5 | import 'package:tirek_mobile/models/response/NeedsTypeResponse.dart';
6 | import 'package:tirek_mobile/services/SharedPreferencesService.dart';
7 | import 'package:tirek_mobile/models/response/HospitalResponse.dart';
8 | import 'dart:convert';
9 |
10 | abstract class NeedsRequestService {
11 | Future post(Hospital hospital, NeedsType needType,
12 | String reserveAmount, String requestAmount, String requestAmountMonth);
13 | }
14 |
15 | class TirekNeedsRequestService implements NeedsRequestService {
16 | final SharedPreferencesService sharedPreferencesService;
17 |
18 | TirekNeedsRequestService(this.sharedPreferencesService);
19 |
20 | @override
21 | Future post(
22 | Hospital hospital,
23 | NeedsType needType,
24 | String reserveAmount,
25 | String requestAmount,
26 | String requestAmountMonth) async {
27 | final userInfo = await sharedPreferencesService.getCurrentUserInfo();
28 |
29 | final body = json.encode({
30 | "hospital_id": hospital.id,
31 | "need_type_id": needType.id,
32 | "reserve_amount": reserveAmount,
33 | "request_amount": requestAmount,
34 | "request_amount_month": requestAmountMonth
35 | });
36 |
37 | final Map headers = {
38 | 'Authorization': "Token ${userInfo.token}",
39 | 'Content-Type': 'application/json; charset=utf-8',
40 | 'Accept': 'application/json; charset=utf-8',
41 | };
42 |
43 | final responseJson = await ApiHelper.post("hospital-needs/", headers, body);
44 | return NeedsRequestResponse.fromJson(responseJson);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/public/whatsapp.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
47 |
--------------------------------------------------------------------------------
/mobile/lib/services/SharedPreferencesService.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 | import 'package:tirek_mobile/models/response/AuthenticationResponse.dart';
3 | import 'package:tirek_mobile/models/response/User.dart';
4 |
5 | abstract class SharedPreferencesService {
6 | saveAuthenticationResponse(AuthenticationResponse authenticationResponse);
7 |
8 | Future getCurrentUserInfo();
9 |
10 | isLoggedIn();
11 |
12 | removeCurrentUserInfo();
13 | }
14 |
15 | class TirekSharedPreferencesService implements SharedPreferencesService {
16 | @override
17 | saveAuthenticationResponse(
18 | AuthenticationResponse authenticationResponse) async {
19 | SharedPreferences prefs = await SharedPreferences.getInstance();
20 |
21 | await prefs.setString('token', authenticationResponse.token);
22 | await prefs.setInt('userId', authenticationResponse.user.id);
23 | await prefs.setString('userFullName', authenticationResponse.user.fullName);
24 | }
25 |
26 | @override
27 | Future getCurrentUserInfo() async {
28 | SharedPreferences prefs = await SharedPreferences.getInstance();
29 | final token = prefs.getString('token');
30 | final userId = prefs.getInt('userId');
31 | final fullName = prefs.getString('userFullName');
32 |
33 | return AuthenticationResponse(token, User(userId, fullName));
34 | }
35 |
36 | @override
37 | isLoggedIn() async {
38 | SharedPreferences prefs = await SharedPreferences.getInstance();
39 | final token = prefs.getString('token');
40 | return token != null;
41 | }
42 |
43 | @override
44 | removeCurrentUserInfo() async {
45 | SharedPreferences prefs = await SharedPreferences.getInstance();
46 | await prefs.remove('token');
47 | await prefs.remove('userId');
48 | await prefs.remove('userFullName');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0014_auto_20200327_2104.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-27 21:04
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('distributor', '0013_auto_20200327_2023'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='StatisticCategory',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=200, unique=True, verbose_name='Name')),
19 | ('name_ru', models.CharField(max_length=200, null=True, unique=True, verbose_name='Name')),
20 | ('name_ky', models.CharField(max_length=200, null=True, unique=True, verbose_name='Name')),
21 | ],
22 | options={
23 | 'verbose_name': 'Statistic Category',
24 | 'verbose_name_plural': 'Statistic Categories',
25 | },
26 | ),
27 | migrations.AlterModelOptions(
28 | name='needtype',
29 | options={'verbose_name': 'Need Type', 'verbose_name_plural': 'Need Types'},
30 | ),
31 | migrations.RemoveField(
32 | model_name='statistic',
33 | name='name',
34 | ),
35 | migrations.RemoveField(
36 | model_name='statistic',
37 | name='name_ky',
38 | ),
39 | migrations.RemoveField(
40 | model_name='statistic',
41 | name='name_ru',
42 | ),
43 | migrations.AddField(
44 | model_name='statistic',
45 | name='category',
46 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='distributor.StatisticCategory', verbose_name='Category'),
47 | ),
48 | ]
49 |
--------------------------------------------------------------------------------
/frontend/components/contacts/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 | import {bindActionCreators} from 'redux';
4 | import {
5 | fetchContacts as fetchContactsAction,
6 | sendMessage as sendMessageAction,
7 | init as initAction
8 | } from '../../actions/creators/contacts';
9 | import {Col, Container, Row} from 'react-bootstrap'
10 | import Info from "./Info";
11 | import Message from "./Message";
12 |
13 | class Contacts extends Component {
14 | componentDidMount() {
15 | this.props.fetchContactsAction();
16 | this.props.initAction();
17 | }
18 |
19 | render() {
20 | const {data, sending, status, sendMessageAction} = this.props;
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 |
44 | const mapStateToProps = (state) => {
45 | return {
46 | sending: state.contacts.sending,
47 | status: state.contacts.status,
48 | data: state.contacts.data,
49 | }
50 | };
51 |
52 | const mapDispatchToProps = (dispatch) => bindActionCreators({
53 | fetchContactsAction,
54 | sendMessageAction,
55 | initAction
56 | }, dispatch);
57 |
58 | export default connect(mapStateToProps, mapDispatchToProps)(Contacts)
59 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-25 17:51
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='NeedType',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('name', models.CharField(max_length=200, unique=True, verbose_name='Name')),
22 | ],
23 | ),
24 | migrations.CreateModel(
25 | name='Need',
26 | fields=[
27 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28 | ('name', models.CharField(max_length=200, verbose_name='Name')),
29 | ('price_per_piece', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Price per piece (KGS)')),
30 | ('modified_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Modified Date')),
31 | ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created Date')),
32 | ('created_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='need_created_by', to=settings.AUTH_USER_MODEL)),
33 | ('modified_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='need_modified_by', to=settings.AUTH_USER_MODEL)),
34 | ('type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.NeedType', verbose_name='Type')),
35 | ],
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0051_add_manager_group.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-04-20 18:52
2 | from django.db import migrations
3 | from django.contrib.auth.management import create_permissions
4 |
5 |
6 | def add_group(apps, schema_editor):
7 | permissions = [
8 | 'add_hospital',
9 | 'change_hospital',
10 | 'view_hospital',
11 |
12 | 'add_hospitalphonenumber',
13 | 'change_hospitalphonenumber',
14 | 'delete_hospitalphonenumber',
15 | 'view_hospitalphonenumber',
16 |
17 | 'add_hospitalneeds',
18 | 'change_hospitalneeds',
19 | 'delete_hospitalneeds',
20 | 'view_hospitalneeds',
21 |
22 | 'add_statistic',
23 | 'change_statistic',
24 | 'delete_statistic',
25 | 'view_statistic',
26 |
27 | 'add_distributiondetail',
28 | 'change_distributiondetail',
29 | 'delete_distributiondetail',
30 | 'view_distributiondetail',
31 |
32 | 'add_distribution',
33 | 'change_distribution',
34 | 'delete_distribution',
35 | 'view_distribution',
36 | ]
37 |
38 | # Костыль из https://stackoverflow.com/a/31735043/490502
39 | for app_config in apps.get_app_configs():
40 | app_config.models_module = True
41 | create_permissions(app_config, apps=apps, verbosity=0)
42 | app_config.models_module = None
43 |
44 | group, created = apps.get_model('auth', 'Group').objects.get_or_create(name="Hospital Managers")
45 | if created:
46 | print(f'{group} Group created')
47 | for permission in permissions:
48 | group.permissions.add(apps.get_model('auth', 'Permission').objects.get(codename=permission))
49 | print(f'Permitting {group} to {permission}')
50 |
51 |
52 | class Migration(migrations.Migration):
53 | dependencies = [
54 | ('distributor', '0050_auto_20200420_1852'),
55 | ]
56 |
57 | operations = [
58 | migrations.RunPython(add_group)
59 | ]
60 |
--------------------------------------------------------------------------------
/backend/distributor/translation.py:
--------------------------------------------------------------------------------
1 | from modeltranslation.translator import translator, TranslationOptions
2 |
3 | from .models import (
4 | Measure, NeedType, Donation,
5 | Hospital, Region, District,
6 | Locality, StatisticCategory, Page, ContactInfo, ContactMessage)
7 |
8 |
9 | class MeasureTranslationOptions(TranslationOptions):
10 | fields = ('name',)
11 |
12 |
13 | translator.register(Measure, MeasureTranslationOptions)
14 |
15 |
16 | class NeedTypeTranslationOptions(TranslationOptions):
17 | fields = ('name',)
18 |
19 |
20 | translator.register(NeedType, NeedTypeTranslationOptions)
21 |
22 |
23 | class DonationTranslationOptions(TranslationOptions):
24 | fields = ('description', 'donator_name')
25 |
26 |
27 | translator.register(Donation, DonationTranslationOptions)
28 |
29 |
30 | class HospitalTranslationOptions(TranslationOptions):
31 | fields = ('name',)
32 |
33 |
34 | translator.register(Hospital, HospitalTranslationOptions)
35 |
36 |
37 | class RegionTranslationOptions(TranslationOptions):
38 | fields = ('name',)
39 |
40 |
41 | translator.register(Region, RegionTranslationOptions)
42 |
43 |
44 | class DistrictTranslationOptions(TranslationOptions):
45 | fields = ('name',)
46 |
47 |
48 | translator.register(District, DistrictTranslationOptions)
49 |
50 |
51 | class LocalityTranslationOptions(TranslationOptions):
52 | fields = ('name',)
53 |
54 |
55 | translator.register(Locality, LocalityTranslationOptions)
56 |
57 |
58 | class StatisticCategoryTranslationOptions(TranslationOptions):
59 | fields = ('name',)
60 |
61 |
62 | translator.register(StatisticCategory, StatisticCategoryTranslationOptions)
63 |
64 |
65 | class PageTranslationOptions(TranslationOptions):
66 | fields = ('name', 'content')
67 |
68 |
69 | translator.register(Page, PageTranslationOptions)
70 |
71 |
72 | class ContactInfoTranslationOptions(TranslationOptions):
73 | fields = ('text',)
74 |
75 |
76 | translator.register(ContactInfo, ContactInfoTranslationOptions)
77 |
--------------------------------------------------------------------------------
/template/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
18 |
19 |
28 | COVID 19
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/mobile/lib/pages/RootPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:tirek_mobile/pages/LoginPage.dart';
3 | import 'package:tirek_mobile/services/AuthenticationService.dart';
4 | import 'package:tirek_mobile/services/SharedPreferencesService.dart';
5 |
6 | enum AuthStatus {
7 | NOT_DETERMINED,
8 | NOT_LOGGED_IN,
9 | LOGGED_IN,
10 | }
11 |
12 | class RootPage extends StatefulWidget {
13 | RootPage({this.sharedPreferencesService, this.authenticationService});
14 |
15 | final SharedPreferencesService sharedPreferencesService;
16 | final AuthenticationService authenticationService;
17 |
18 | @override
19 | State createState() => new _RootPageState();
20 | }
21 |
22 | class _RootPageState extends State {
23 | AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
24 |
25 | @override
26 | void initState() {
27 | super.initState();
28 | // widget.auth.getCurrentUser().then((user) {
29 | // setState(() {
30 | // if (user != null) {
31 | // _userId = user?.uid;
32 | // }
33 | // authStatus =
34 | // user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
35 | // });
36 | // });
37 | }
38 |
39 | void loginCallback() {
40 | // widget.auth.getCurrentUser().then((user) {
41 | // setState(() {
42 | // _userId = user.uid.toString();
43 | // });
44 | // });
45 | // setState(() {
46 | // authStatus = AuthStatus.LOGGED_IN;
47 | // });
48 | }
49 |
50 | void logoutCallback() {
51 | setState(() {
52 | authStatus = AuthStatus.NOT_LOGGED_IN;
53 | });
54 | }
55 |
56 | Widget buildWaitingScreen() {
57 | return Scaffold(
58 | body: Container(
59 | alignment: Alignment.center,
60 | child: CircularProgressIndicator(),
61 | ),
62 | );
63 | }
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | return new LoginPage(
68 | authenticationService: widget.authenticationService,
69 | sharedPreferencesService: widget.sharedPreferencesService,
70 | loginCallback: loginCallback,
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/mobile/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/frontend/components/donations/donation.js:
--------------------------------------------------------------------------------
1 | import {Button, Card, Table} from "react-bootstrap";
2 | import React, {Fragment} from "react";
3 | import Link from "../navigation/ActiveLink";
4 |
5 | const Donation = (props) => {
6 | const hideLink = !!props.hideLink;
7 | const item = props.donation;
8 | const details = (item.details || []).map(detail => {
9 | const key = `${item.id} ${detail.id}`
10 | return (
11 |
12 | |
13 |
14 | {detail.need_type.name}
15 |
16 | |
17 |
18 |
19 | {detail.amount} {detail.need_type.measure.name}
20 |
21 | |
22 |
23 | )
24 | });
25 |
26 | const map = {
27 | ORGANIZATION: 'Организация',
28 | PERSONAL: 'Частное лицо',
29 | DONOR: 'Донор',
30 | GOVERNMENT: 'Государство',
31 | }
32 |
33 |
34 | return (
35 |
36 |
37 |
38 | {item.donator_name}
39 |
40 |
41 | Тип: {map[item.donator_type] || 'Неизвестно'}
42 | {item.description}
43 |
44 |
45 |
46 |
47 |
48 | | Наименование |
49 | Кол-во |
50 |
51 |
52 |
53 | {details}
54 |
55 |
56 |
57 | {!hideLink &&
58 |
59 |
60 |
61 | }
62 |
63 |
64 | )
65 | }
66 |
67 | export default Donation;
--------------------------------------------------------------------------------
/frontend/components/donations/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux'
3 | import {bindActionCreators} from 'redux'
4 | import Donation from './donation'
5 | import {Alert, Col, Container, Row, Spinner} from 'react-bootstrap'
6 | import {fetchDonations as fetchDonationsAction,} from '../../actions/creators/donations'
7 |
8 | class Donations extends Component {
9 | componentDidMount() {
10 | this.props.fetchDonationsAction();
11 | }
12 |
13 | render() {
14 | const {fetching, results} = this.props;
15 |
16 | return (
17 |
18 |
19 |
20 | {fetching && }
21 |
22 |
23 | Список пожертвований
24 |
25 |
26 | {
27 | !fetching && results.length > 0 &&
28 | results.map(item =>
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | {
36 | !fetching && !results.length &&
37 | Нет данных
38 |
39 | }
40 |
41 |
42 |
43 |
44 | );
45 | }
46 | }
47 |
48 | const mapStateToProps = (state) => {
49 | return {
50 | fetching: state.donations.fetching,
51 | results: state.donations.results
52 | }
53 | };
54 |
55 | const mapDispatchToProps = (dispatch) => bindActionCreators({
56 | fetchDonationsAction,
57 | }, dispatch);
58 |
59 | export default connect(mapStateToProps, mapDispatchToProps)(Donations)
60 |
--------------------------------------------------------------------------------
/backend/distributor/migrations/0004_donation_donationdetail.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.4 on 2020-03-25 19:24
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ('distributor', '0003_auto_20200325_1806'),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Donation',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('donator_type', models.CharField(choices=[('ORGANIZATION', 'ORGANIZATION'), ('PERSONAL', 'PERSONAL')], default='ORGANIZATION', max_length=12, verbose_name='Donator Type')),
21 | ('donator_name', models.CharField(max_length=200, verbose_name='Donator Name')),
22 | ('description', models.TextField(max_length=1000, verbose_name='Donation Description')),
23 | ('modified_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Modified Date')),
24 | ('created_at', models.DateTimeField(verbose_name='Created Date')),
25 | ('created_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='donation_created_by', to=settings.AUTH_USER_MODEL)),
26 | ('modified_by', models.ForeignKey(auto_created=True, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='donation_modified_by', to=settings.AUTH_USER_MODEL)),
27 | ],
28 | ),
29 | migrations.CreateModel(
30 | name='DonationDetail',
31 | fields=[
32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
33 | ('amount', models.PositiveSmallIntegerField(verbose_name='Amount')),
34 | ('donation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.Donation', verbose_name='Donation)')),
35 | ('need_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='distributor.NeedType', verbose_name='Need Type')),
36 | ],
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------