Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Financial reports and expenses list for a campaign #1383

Merged
merged 12 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions public/locales/bg/campaigns.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
"status": "Статус:",
"messages": "Послания:",
"gallery": "Галерия",
"report-irregularity": "Сигнализирай за злоупотреба",
"financial-report": "Финансови отчети",
"report-campaign": "Докладвайте кампанията",
"feedback": "Обратна връзка",
"images": {
Expand Down
38 changes: 35 additions & 3 deletions public/locales/bg/expenses.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"fields-error": {
"amount-unavailable": "Недостатъчна наличност в трезора!"
},
"errors": {
"no-default-vault": "Не е избран трезор по подразбиране!"
},
"fields": {
"type": "Тип",
"status": "Статус",
Expand All @@ -14,8 +17,30 @@
"documentId": "Документ Id",
"approvedById": "Одобрен от (Id)",
"approvedBy": "Одобрен от",
"approved": "Одобрен",
"action": "Действие",
"empty": "Празно"
"empty": "Празно",
"date": "Дата",
"attached-files": "Прикачени файлове",
"files": "Файлове"
},
"field-types": {
"none": "Няма",
"internal": "Вътрешен",
"operating": "Оперативен",
"administrative": "Административен",
"medical" : "Медицински",
"services" : "Услуги",
"groceries" : "Хранителни",
"transport" : "Транспорт",
"accommodation" : "Жилищни",
"shipping" : "Доставки",
"utility" : "Комунални",
"rental" : "Наеми",
"legal" : "Юридически",
"bank" : "Банкови",
"advertising" : "Рекламни",
"other" : "Други"
},
"alerts": {
"new-row": {
Expand All @@ -35,7 +60,9 @@
"question": "Желаете ли да изтриете избраните разходи",
"error": "Възникна грешка при опит за изтриване на разходи.",
"success": "Разходите са изтрити успешно!"
}
},
"delete": "Изтрит разход",
"no-files-uploaded": "Няма прикачени файлове!"
},
"btns": {
"add": "Добави",
Expand All @@ -52,10 +79,15 @@
"info": "Информация за разход"
},
"description": "Всички разходи",
"reported": "Общо отчетени",
"uploaded-documents": "Прикачени документи",
"uploaded-files": "Прикачени файлове",
"deleteTitle": "Сигурни ли сте, че искате да изтриете файла?",
"tooltips": {
"add": "Добави",
"view": "Преглед",
"edit": "Редактирай",
"delete": "Изтрий"
"delete": "Изтрий",
"download": "Свали"
}
}
2 changes: 2 additions & 0 deletions public/locales/en/campaigns.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
"profile": "Profile:",
"status": "Status:",
"gallery": "Gallery",
"report-irregularity": "Report irregularity",
"financial-report": "Financial report",
"report-campaign": "Report the campaign",
"feedback": "Feedback",
"images": {
Expand Down
19 changes: 16 additions & 3 deletions public/locales/en/expenses.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"fields-error": {
"amount-unavailable": "Insufficient stock in the vault!"
},
"errors": {
"no-default-vault": "No default vault found!"
},
"fields": {
"type": "Type",
"status": "Status",
Expand All @@ -13,7 +16,10 @@
"approvedById": "Approved by (Id)",
"approvedBy": "Approved by",
"action": "Action",
"empty": "Empty"
"empty": "Empty",
"date": "Date",
"attached-files": "Attached files",
"files": "Files"
},
"alerts": {
"new-row": {
Expand All @@ -33,7 +39,9 @@
"question": "Are you sure you want to delete multiple expenses",
"error": "Error occurred trying to delete multiple expenses.",
"success": "Expenses were deleted successfully!!"
}
},
"delete": "Deleted expense",
"no-files-uploaded": "No files uploaded"
},
"btns": {
"add": "Add",
Expand All @@ -50,10 +58,15 @@
"info": "Expense information"
},
"description": "All expenses",
"reported": "Total reported",
"uploaded-documents": "Uploaded documents",
"uploaded-files": "Uploaded files",
"deleteTitle": "Delete expense file?",
"tooltips": {
"add": "Add",
"view": "View",
"edit": "Edit",
"delete": "Delete"
"delete": "Delete",
"download": "Download"
}
}
22 changes: 22 additions & 0 deletions src/common/hooks/campaigns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'gql/campaigns'
import { DonationStatus } from 'gql/donations.enums'
import { apiClient } from 'service/apiClient'
import { useCurrentPerson } from 'common/util/useCurrentPerson'

// NOTE: shuffling the campaigns so that each gets its fair chance to be on top row
export const campaignsOrderQueryFunction: QueryFunction<CampaignResponse[]> = async ({
Expand Down Expand Up @@ -110,3 +111,24 @@ export function useCampaignDonationHistory(
endpoints.donation.getDonations(campaignId, DonationStatus.succeeded, pageindex, pagesize).url,
])
}

export function useCanEditCampaign(slug: string) {
const { data: session } = useSession()

const { data: userData } = useCurrentPerson()
const { data: campaignData } = useViewCampaign(slug)

if (!session || !session.user) {
return false
}

if (!userData || !campaignData || !userData.user) {
return false
}

const canEdit =
userData.user.id === campaignData.campaign.organizer?.person.id ||
session?.user?.realm_access?.roles?.includes('podkrepi-admin')

return canEdit
}
2 changes: 1 addition & 1 deletion src/common/hooks/donation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function useDonationSession() {
mutationFn: createCheckoutSession,
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'),
retry(failureCount, error) {
retry(failureCount) {
if (failureCount < 4) {
return true
}
Expand Down
38 changes: 37 additions & 1 deletion src/common/hooks/expenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'
import { useSession } from 'next-auth/react'

import { endpoints } from 'service/apiEndpoints'
import { ExpenseResponse } from 'gql/expenses'
import { ExpenseFile, ExpenseResponse } from 'gql/expenses'
import { authQueryFnFactory } from 'service/restRequests'

export function useExpensesList() {
Expand All @@ -19,3 +19,39 @@ export function useViewExpense(id: string) {
queryFn: authQueryFnFactory(session?.accessToken),
})
}

export function useCampaignExpensesList(slug: string) {
const { data: session } = useSession()
return useQuery<ExpenseResponse[]>([endpoints.campaign.listCampaignExpenses(slug).url], {
queryFn: authQueryFnFactory(session?.accessToken),
})
}

export function useCampaignApprovedExpensesList(slug: string) {
const { data: session } = useSession()
return useQuery<ExpenseResponse[]>([endpoints.campaign.listCampaignApprovedExpenses(slug).url], {
queryFn: authQueryFnFactory(session?.accessToken),
})
}

export function useCampaignExpenseFiles(id: string) {
const { data: session } = useSession()

return useQuery<ExpenseFile[]>([endpoints.expenses.listExpenseFiles(id).url], {
queryFn: authQueryFnFactory(session?.accessToken),
})
}

export function useDeleteCampaignExpense(id: string) {
const { data: session } = useSession()
return useQuery<ExpenseFile[]>([endpoints.expenses.deleteExpense(id).url], {
queryFn: authQueryFnFactory(session?.accessToken),
})
}

export function useDeleteExpenseFile(id: string) {
const { data: session } = useSession()
return useQuery<null>([endpoints.expenses.deleteExpenseFile(id).url], {
queryFn: authQueryFnFactory(session?.accessToken),
})
}
9 changes: 9 additions & 0 deletions src/common/hooks/person.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ export function usePerson(id: string) {
authQueryFnFactory<PersonResponse>(session?.accessToken),
)
}

export function useViewPersonByKeylockId(id: string) {
const { data: session } = useSession()

return useQuery(
[endpoints.person.viewPersonByKeylockId(id).url],
authQueryFnFactory<PersonResponse>(session?.accessToken),
)
}
6 changes: 6 additions & 0 deletions src/common/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,13 @@ export const routes = {
index: '/campaigns',
create: '/campaigns/create',
viewCampaignBySlug: (slug: string) => `/campaigns/${slug}`,
viewExpenses: (slug: string) => `/campaigns/${slug}/expenses`,
oneTimeDonation: (slug: string) => `/campaigns/donation/${slug}`,
expenses: {
create: (slug: string) => `/campaigns/${slug}/expenses/create`,
edit: (slug: string, id: string) => `/campaigns/${slug}/expenses/${id}`,
downloadFile: (id: string) => `/expenses/download-files/${id}`,
},
},
donation: {
viewCertificate: (donationId: string) => `/api/pdf/certificate/${donationId}`,
Expand Down
2 changes: 1 addition & 1 deletion src/common/util/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const canViewSupporters = (sessionRoles: SessionRoles): boolean => {
}

export const isAdmin = (session: Session | JWT | null): boolean => {
if (session) {
if (session && session.user && session.user.resource_access && session.user.realm_access) {
const sessionRoles: SessionRoles = {
realmRoles: session.user?.realm_access.roles ?? [],
resourceRoles: session.user?.resource_access?.account.roles ?? [],
Expand Down
17 changes: 11 additions & 6 deletions src/components/admin/GridActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ type Props = {
id: string
name: string
editLink?: string
disableView?: boolean
}

export default function GridActions({ modalStore, id, name, editLink }: Props) {
export default function GridActions({ modalStore, id, name, editLink, disableView }: Props) {
const { t } = useTranslation('admin')
const { showDetails, showDelete, setSelectedRecord } = modalStore

Expand All @@ -31,11 +32,15 @@ export default function GridActions({ modalStore, id, name, editLink }: Props) {

return (
<>
<Tooltip title={t('cta.view') || ''}>
<IconButton size="small" color="primary" onClick={detailsClickHandler}>
<PageviewOutlinedIcon />
</IconButton>
</Tooltip>
{!disableView ? (
<Tooltip title={t('cta.view') || ''}>
<IconButton size="small" color="primary" onClick={detailsClickHandler}>
<PageviewOutlinedIcon />
</IconButton>
</Tooltip>
) : (
''
)}
{editLink ? (
<Link href={editLink} passHref>
<Tooltip title={t('cta.edit') || ''}>
Expand Down
5 changes: 1 addition & 4 deletions src/components/admin/expenses/ExpenseTypeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ export default function ExpenseTypeSelect({ name = 'type' }) {
defaultValue=""
label={t('fields.' + name)}
{...field}>
<MenuItem value="" disabled>
{t('fields.' + name)}
</MenuItem>
{values?.map((value, index) => (
<MenuItem key={index} value={value}>
{value}
{t('expenses:field-types.' + value)}
</MenuItem>
))}
</FormTextField>
Expand Down
2 changes: 2 additions & 0 deletions src/components/admin/expenses/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ export default function Form() {
status: data?.status || ExpenseStatus.pending,
currency: data?.currency || Currency.BGN,
amount: data?.amount || 0,
money: 0,
vaultId: data?.vaultId || '',
deleted: data?.deleted || false,
description: data?.description || '',
documentId: data?.documentId || '',
approvedById: data?.approvedById || '',
spentAt: '',
}

const mutationFn = id ? useEditExpense(id) : useCreateExpense()
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/expenses/grid/GridAppbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const addIconStyles = {
boxShadow: 3,
}

export default function GridAppbar() {
export default function ExpensesGridAppbar() {
const router = useRouter()

return (
Expand Down
10 changes: 10 additions & 0 deletions src/components/client/campaign-expenses/CampaignExpenseCreate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Form from 'components/client/campaign-expenses/Form'
import Layout from 'components/client/layout/Layout'

export default function CreateCampaignExpensePage() {
return (
<Layout maxWidth={false}>
<Form />
</Layout>
)
}
11 changes: 11 additions & 0 deletions src/components/client/campaign-expenses/CampaignExpenseEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Layout from 'components/client/layout/Layout'

import Form from './Form'

export default function ExpensesEditPage() {
return (
<Layout maxWidth={false}>
<Form />
</Layout>
)
}
Loading