diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7ca311..bd71a71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true @@ -52,7 +52,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Setup node - uses: actions/setup-node@v4.1.0 + uses: actions/setup-node@v4.2.0 with: node-version: 20 @@ -74,13 +74,13 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true - name: Setup node - uses: actions/setup-node@v4.1.0 + uses: actions/setup-node@v4.2.0 with: node-version: 20 diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index 0311ce0..67dbfe0 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 1da1c5e..5017fe6 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0fe1147..65f804b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true diff --git a/.github/workflows/remove-review.yml b/.github/workflows/remove-review.yml index 4fcf443..0578d0d 100644 --- a/.github/workflows/remove-review.yml +++ b/.github/workflows/remove-review.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Set up Ruby - uses: ruby/setup-ruby@v1.213.0 + uses: ruby/setup-ruby@v1.215.0 with: ruby-version: .ruby-version bundler-cache: true diff --git a/Gemfile b/Gemfile index 84388c9..bc7f9f8 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "vite_rails", "~> 3.0.19" # Use sqlite3 as the database for Active Record gem "sqlite3" # Use the Puma web server [https://github.com/puma/puma] -gem "puma", ">= 6.5.0" +gem "puma", ">= 6.6.0" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] gem "bcrypt", "~> 3.1.20" diff --git a/Gemfile.lock b/Gemfile.lock index 1ca2334..9967da7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,8 +77,8 @@ GEM ast (2.4.2) authentication-zero (4.0.3) aws-eventstream (1.3.0) - aws-partitions (1.1040.0) - aws-sdk-core (3.216.0) + aws-partitions (1.1044.0) + aws-sdk-core (3.217.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -86,7 +86,7 @@ GEM aws-sdk-kms (1.97.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.178.0) + aws-sdk-s3 (1.179.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -129,8 +129,8 @@ GEM erubi (1.13.1) et-orbi (1.2.11) tzinfo - factory_bot (6.5.0) - activesupport (>= 5.0.0) + factory_bot (6.5.1) + activesupport (>= 6.1.0) factory_bot_rails (6.4.4) factory_bot (~> 6.5) railties (>= 5.0.0) @@ -150,7 +150,8 @@ GEM inertia_rails (3.6.0) railties (>= 6) io-console (0.8.0) - irb (1.14.3) + irb (1.15.1) + pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) @@ -166,7 +167,7 @@ GEM sshkit (>= 1.23.0, < 2.0) thor (~> 1.3) zeitwerk (>= 2.6.18, < 3.0) - language_server-protocol (3.17.0.3) + language_server-protocol (3.17.0.4) lint_roller (1.1.0) logger (1.6.5) loofah (2.24.0) @@ -198,7 +199,7 @@ GEM net-protocol net-protocol (0.2.2) timeout - net-scp (4.0.0) + net-scp (4.1.0) net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) @@ -236,16 +237,19 @@ GEM parser (3.3.7.0) ast (~> 2.4.1) racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) prism (1.3.0) psych (5.2.3) date stringio public_suffix (6.0.1) - puma (6.5.0) + puma (6.6.0) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (3.1.8) + rack (3.1.9) rack-proxy (0.7.7) rack rack-session (2.1.0) @@ -322,7 +326,7 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.37.0) + rubocop-ast (1.38.0) parser (>= 3.3.1.0) rubocop-performance (1.23.1) rubocop (>= 1.48.1, < 2.0) @@ -332,14 +336,14 @@ GEM rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - ruby-lsp (0.23.6) + ruby-lsp (0.23.8) language_server-protocol (~> 3.17.0) prism (>= 1.2, < 2.0) rbs (>= 3, < 4) sorbet-runtime (>= 0.5.10782) - ruby-lsp-rails (0.3.30) + ruby-lsp-rails (0.3.31) ruby-lsp (>= 0.23.0, < 0.24.0) - ruby-lsp-rspec (0.1.20) + ruby-lsp-rspec (0.1.21) ruby-lsp (~> 0.23.0) ruby-progressbar (1.13.0) rubyzip (2.4.1) @@ -350,7 +354,7 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - solid_cable (3.0.5) + solid_cable (3.0.7) actioncable (>= 7.2) activejob (>= 7.2) activerecord (>= 7.2) @@ -359,14 +363,14 @@ GEM activejob (>= 7.2) activerecord (>= 7.2) railties (>= 7.2) - solid_queue (1.1.2) + solid_queue (1.1.3) activejob (>= 7.1) activerecord (>= 7.1) concurrent-ruby (>= 1.3.1) fugit (~> 1.11.0) railties (>= 7.1) thor (~> 1.3.1) - sorbet-runtime (0.5.11766) + sorbet-runtime (0.5.11798) sqlite3 (2.5.0-aarch64-linux-gnu) sqlite3 (2.5.0-aarch64-linux-musl) sqlite3 (2.5.0-arm-linux-gnu) @@ -466,7 +470,7 @@ DEPENDENCIES kamal mailjet (~> 1.8) overmind - puma (>= 6.5.0) + puma (>= 6.6.0) rails rotp (~> 6.3) rspec-rails diff --git a/app/controllers/users/contributions_controller.rb b/app/controllers/users/contributions_controller.rb index b6ca8b5..6c1d357 100644 --- a/app/controllers/users/contributions_controller.rb +++ b/app/controllers/users/contributions_controller.rb @@ -16,7 +16,11 @@ def index def new render inertia: "Contribution/New", props: { causes: Cause.pluck(:name, :id).to_h, - labels: Label.pluck(:id, :name).map { |id, name| {value: id, label: name} } + labels: Label.pluck(:id, :name).map { |id, name| {value: id, label: name} }, + location_types: Location.types.keys, + document_types: Document.types.keys, + fund_source_types: FundSource.types.keys, + fund_allocation_types: FundAllocation.types.keys } end @@ -51,7 +55,6 @@ def osbl_params :public_utility, :creation_year, {osbls_labels_attributes: [:label_id]}, - :contact_email, {annual_finances_attributes: [ :year, :certified, @@ -77,6 +80,21 @@ def osbl_params :year, :description ]} + ]}, + {locations_attributes: [ + :type, + :name, + :description, + :website, + {address_attributes: [ + :street_number, + :street_name, + :additional_info, + :postal_code, + :city, + :latitude, + :longitude + ]} ]} ) end diff --git a/app/frontend/Layout.tsx b/app/frontend/Layout.tsx index 9e0673d..ed6a8dd 100644 --- a/app/frontend/Layout.tsx +++ b/app/frontend/Layout.tsx @@ -9,14 +9,14 @@ import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar' const flashTypes = ['message', 'success', 'info', 'warning', 'error'] as const interface LayoutProps { - children: ReactElement - showSidebar?: boolean + showSidebar: boolean flash: { [key in typeof flashTypes[number]]?: string } + children?: ReactElement } export default function Layout ({ children, - showSidebar = false, + showSidebar, flash = {} }: LayoutProps): ReactElement { useEffect(() => { diff --git a/app/frontend/components/forms/MyInput.tsx b/app/frontend/components/forms/MyInput.tsx deleted file mode 100644 index b295e7f..0000000 --- a/app/frontend/components/forms/MyInput.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ReactElement, ReactNode } from 'react' -import { Label } from '@/components/ui/label' -import { Input } from '@/components/ui/input' -import InputError from '@/components/forms/InputError' - -interface BaseMyInputProps { - type: string - required?: boolean - disabled?: boolean - value?: string - onChange: (e: React.ChangeEvent) => void - autoFocus?: boolean - error?: string - id: string -} - -interface WithLabel extends BaseMyInputProps { - labelText: string | ReactNode - placeholder?: string -} - -interface WithoutLabel extends BaseMyInputProps { - labelText?: undefined - placeholder: string -} - -type MyInputProps = WithLabel | WithoutLabel - -export default function MyInput (props: MyInputProps): ReactElement { - const { id, labelText, type, required, disabled, value, onChange, placeholder, autoFocus, error } = props - const errorClass = (error !== null && error !== undefined && error !== '') ? 'border-red-600' : '' - - return ( -
- { - (labelText !== undefined && labelText !== '') - ? - : - } - - - {Boolean(error) && {error}} -
- ) -} diff --git a/app/frontend/components/layout/AppSidebar.tsx b/app/frontend/components/layout/AppSidebar.tsx index 6c266a2..39bfc58 100644 --- a/app/frontend/components/layout/AppSidebar.tsx +++ b/app/frontend/components/layout/AppSidebar.tsx @@ -1,6 +1,7 @@ import { FilePlus2, Library, NotebookPen } from 'lucide-react' -import { Link, usePage } from '@inertiajs/react' +import { usePage } from '@inertiajs/react' import { ReactElement } from 'react' +import MyLink from '@/components/shared/MyLink' import { Sidebar, @@ -9,7 +10,8 @@ import { SidebarGroupContent, SidebarMenu, SidebarMenuButton, - SidebarMenuItem + SidebarMenuItem, + useSidebar } from '@/components/ui/sidebar' const items = [ @@ -32,6 +34,13 @@ const items = [ export default function AppSidebar (): ReactElement { const { url } = usePage() + const { setOpenMobile, isMobile } = useSidebar() + + const handleClick = (): void => { + if (isMobile) { + setOpenMobile(false) + } + } return ( @@ -42,17 +51,18 @@ export default function AppSidebar (): ReactElement { {items.map((item) => ( - {item.title} - + ))} diff --git a/app/frontend/components/layout/Footer.tsx b/app/frontend/components/layout/Footer.tsx index 5586331..dc8f142 100644 --- a/app/frontend/components/layout/Footer.tsx +++ b/app/frontend/components/layout/Footer.tsx @@ -1,6 +1,5 @@ -import { Link } from '@inertiajs/react' import { ReactElement } from 'react' - +import MyLink from '@/components/shared/MyLink' import { NavigationMenu, NavigationMenuItem, @@ -58,7 +57,7 @@ export default function Footer (): ReactElement { return (
- +

Benefactorum @@ -67,19 +66,19 @@ export default function Footer (): ReactElement { Association de bienfaiteurs

- +
{navLinks.map((link) => ( - {link.title} - + ))} @@ -103,13 +102,13 @@ export default function Footer (): ReactElement {

{new Date().getFullYear()} -   - + Mentions légales - +  -  - + Données personnelles - +

) diff --git a/app/frontend/components/layout/Header.tsx b/app/frontend/components/layout/Header.tsx index 4c4f77d..914b3b8 100644 --- a/app/frontend/components/layout/Header.tsx +++ b/app/frontend/components/layout/Header.tsx @@ -1,4 +1,5 @@ -import { Link, router, usePage } from '@inertiajs/react' +import { router, usePage } from '@inertiajs/react' +import MyLink from '@/components/shared/MyLink' import { ReactElement, useState, useEffect, useRef } from 'react' import { Spin as Hamburger } from 'hamburger-react' import { @@ -89,7 +90,7 @@ export default function Header (): ReactElement { id='mainHeader' className='2xl:container 2xl:mx-auto flex items-center px-2 justify-between' > - - + - Trouver une association - + {user != null && ( - Mes contributions - + )} {user === null && ( - Qui sommes-nous ? - + )} @@ -171,7 +172,7 @@ export default function Header (): ReactElement { {user !== null && ( - Qui sommes-nous ? - + )} {subNavLinks.map((link) => ( - {link.title} - + ))} @@ -217,12 +218,12 @@ export default function Header (): ReactElement {
{user === null && !isExcluded && ( - + - + )} {user !== null && ( - +
diff --git a/app/frontend/components/forms/ConnectionForm.tsx b/app/frontend/components/pages/auth/connection/ConnectionForm.tsx similarity index 96% rename from app/frontend/components/forms/ConnectionForm.tsx rename to app/frontend/components/pages/auth/connection/ConnectionForm.tsx index a9fa490..f46ce24 100644 --- a/app/frontend/components/forms/ConnectionForm.tsx +++ b/app/frontend/components/pages/auth/connection/ConnectionForm.tsx @@ -1,7 +1,7 @@ import { ReactElement } from 'react' import useFormHandler from '@/hooks/useFormHandler' import { z } from 'zod' -import MyInput from './MyInput' +import MyInput from '@/components/shared/MyInput' import { Button } from '@/components/ui/button' import { StepForward } from 'lucide-react' diff --git a/app/frontend/components/forms/SignInForm.tsx b/app/frontend/components/pages/auth/signIn/SignInForm.tsx similarity index 97% rename from app/frontend/components/forms/SignInForm.tsx rename to app/frontend/components/pages/auth/signIn/SignInForm.tsx index e6db47c..2cf170e 100644 --- a/app/frontend/components/forms/SignInForm.tsx +++ b/app/frontend/components/pages/auth/signIn/SignInForm.tsx @@ -9,7 +9,7 @@ import { } from '@/components/ui/input-otp' import { REGEXP_ONLY_DIGITS } from 'input-otp' import { z } from 'zod' -import InputError from '@/components/forms/InputError' +import InputError from '@/components/shared/InputError' import { Button } from '@/components/ui/button' import { StepForward } from 'lucide-react' import ResendCode from '@/components/pages/auth/signIn/ResendCode' diff --git a/app/frontend/components/forms/SignUpForm.tsx b/app/frontend/components/pages/auth/signUp/SignUpForm.tsx similarity index 97% rename from app/frontend/components/forms/SignUpForm.tsx rename to app/frontend/components/pages/auth/signUp/SignUpForm.tsx index cda691c..2d383b0 100644 --- a/app/frontend/components/forms/SignUpForm.tsx +++ b/app/frontend/components/pages/auth/signUp/SignUpForm.tsx @@ -2,8 +2,8 @@ import { useRef, ReactElement, FormEvent } from 'react' import { Link } from '@inertiajs/react' import useFormHandler from '@/hooks/useFormHandler' import { KeysWithStringValues } from '@/types/utilityTypes' -import MyInput from './MyInput' -import MyCheckbox from './MyCheckbox' +import MyInput from '@/components/shared/MyInput' +import MyCheckbox from '@/components/shared/MyCheckbox' import ReCAPTCHA from 'react-google-recaptcha' import { Button } from '@/components/ui/button' import { StepForward } from 'lucide-react' diff --git a/app/frontend/components/pages/contribution/new/FundManagementSection.tsx b/app/frontend/components/pages/contribution/new/FundManagementSection.tsx deleted file mode 100644 index 354e94d..0000000 --- a/app/frontend/components/pages/contribution/new/FundManagementSection.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Fragment, ReactElement } from 'react' -import { FundRecord } from '@/pages/Contribution/types' -import { Label } from '@/components/ui/label' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, - SelectSeparator -} from '@/components/ui/select' -import { PlusIcon, TrashIcon } from 'lucide-react' -import { Button } from '@/components/ui/button' -import MyNumberInput from '@/components/forms/MyNumberInput' -import InputError from '@/components/forms/InputError' - -interface FundManagementSectionProps { - title: string - items: FundRecord[] - typeList: Array<{ value: string, label: string, group: string }> - baseErrorPath: string - errors: Record - onUpdate: (items: FundRecord[]) => void - clearErrors: (path: string) => void -} - -export default function FundManagementSection ({ - title, - items, - typeList, - baseErrorPath, - errors, - onUpdate, - clearErrors -}: FundManagementSectionProps): ReactElement { - function handleFundChange ( - index: number, - field: keyof FundRecord, - value: any - ): void { - const updatedItems = items.map((item: FundRecord, i: number) => - i === index ? { ...item, [field]: value } : item - ) - - onUpdate(updatedItems) - - if (field === 'percent' && value !== '') { - clearErrors(`${baseErrorPath}.total_percent`) - } - } - - function handleAdd (e: React.MouseEvent): void { - e.preventDefault() - onUpdate([...items, {}]) - } - - function handleRemove ( - e: React.MouseEvent, - index: number - ): void { - e.preventDefault() - const updatedItems = items.filter((_, i) => i !== index) - onUpdate(updatedItems) - } - - return ( -
-
- - -
- - {Boolean(errors[`${baseErrorPath}.total_percent`]) && ( - - {errors[`${baseErrorPath}.total_percent`]} - - )} - - {items?.map((item, index) => ( -
- - - handleFundChange(index, 'percent', e.target.value)} - placeholder='% *' - suffix='%' - required - /> - - handleFundChange(index, 'amount', e.target.value)} - placeholder='Montant' - suffix='€' - /> - - -
- ))} -
- ) -} diff --git a/app/frontend/components/pages/contribution/new/OsblDataSheet.tsx b/app/frontend/components/pages/contribution/new/OsblDataSheet.tsx deleted file mode 100644 index 54f6b6d..0000000 --- a/app/frontend/components/pages/contribution/new/OsblDataSheet.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { ReactElement } from 'react' -import { Label } from '@/components/ui/label' -import MyInput from '@/components/forms/MyInput' -import { - MultiSelect -} from '@/components/ui/multi-select' -import { FormProps } from '@/pages/Contribution/types' -import { Baby, Stethoscope, Coins, BookMarked, PawPrint, Trees, Church, Microscope, Globe, Accessibility, Scale, Shuffle, Brush } from 'lucide-react' -import InputError from '@/components/forms/InputError' -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue -} from '@/components/ui/select' -import { usePage } from '@inertiajs/react' -import KeywordAsyncCreatableSelect from './KeywordAsyncCreatableSelect' -import HelpTooltip from '@/components/shared/HelpTooltip' -import InterventionAreaAsyncCreatableSelect from './InterventionAreaAsyncCreatableSelect' -import MyNumberInput from '@/components/forms/MyNumberInput' -import MyCheckbox from '@/components/forms/MyCheckbox' - -const CausesList = [ - { value: 'environnement', label: 'Environnement', icon: Trees }, - { value: 'protection-de-l-enfance', label: 'Protection de l\'enfance', icon: Baby }, - { value: 'sante', label: 'Santé', icon: Stethoscope }, - { value: 'lutte-contre-la-précarité', label: 'Lutte contre la précarité', icon: Coins }, - { value: 'education', label: 'Éducation', icon: BookMarked }, - { value: 'protection-animale', label: 'Protection animale', icon: PawPrint }, - { value: 'recherche', label: 'Recherche', icon: Microscope }, - { value: 'arts-culture-patrimoine', label: 'Arts, Culture, Patrimoine', icon: Brush }, - { value: 'aide-internationale', label: 'Aide internationale', icon: Globe }, - { value: 'handicap', label: 'Handicap', icon: Accessibility }, - { value: 'justice-sociale', label: 'Justice sociale', icon: Scale }, - { value: 'religion', label: 'Religion', icon: Church }, - { value: 'autre', label: 'Autre', icon: Shuffle } -] - -const GeographicalScaleList = [ - { value: 'local', label: 'Locale' }, - { value: 'regional', label: 'Régionale' }, - { value: 'national', label: 'Nationale' }, - { value: 'international', label: 'Internationale' } -] - -const OsblTypeList = [ - { value: 'association', label: 'Association' }, - { value: 'fonds_de_dotation', label: 'Fonds de dotation' }, - { value: 'fondation', label: 'Fondation' } -] - -export default function OsblDataSheet ({ data, setData, errors, clearErrors }: FormProps): ReactElement { - const causes = usePage().props.causes as Record - const labels = usePage().props.labels as Array<{ value: string, label: string }> - - const syncedCausesList = CausesList.map(cause => ({ - ...cause, - value: String(causes[cause.value]) - })) - - return ( -
-
-

Fiche technique

- -
-
- - { - setData('osbls_causes_attributes', value.map(cause => ({ cause_id: cause }))) - clearErrors('osbls_causes_attributes') - }} - placeholder='' - variant='secondary' - animation={0} - maxCount={1} - /> - {Boolean(errors.osbls_causes_attributes) && {errors.osbls_causes_attributes}} -
- -
- - { - setData('tax_reduction', value) - clearErrors('tax_reduction') - }} - > -
- - -
-
- - -
-
- {Boolean(errors.tax_reduction) && {errors.tax_reduction}} -
- -
- - -
- -
- - -
- -
- - -
- -
- - - - {data.osbl_type === 'association' && ( - setData('public_utility', checked)} - > - Reconnue d'utilité publique (ARUP) - - )} -
- - { - setData('creation_year', e.target.value) - }} - /> - -
- - { - setData('osbls_labels_attributes', value.map(label => ({ label_id: label }))) - clearErrors('osbls_labels_attributes') - }} - placeholder='' - variant='default' - animation={0} - maxCount={2} - /> -
- - { - setData('contact_email', e.target.value) - clearErrors('contact_email') - }} - error={errors.contact_email} - /> -
-
-
- ) -} diff --git a/app/frontend/components/pages/contribution/new/OsblDatasheet.tsx b/app/frontend/components/pages/contribution/new/OsblDatasheet.tsx new file mode 100644 index 0000000..21009fc --- /dev/null +++ b/app/frontend/components/pages/contribution/new/OsblDatasheet.tsx @@ -0,0 +1,221 @@ +import { ReactElement } from 'react' +import { Label } from '@/components/ui/label' +import { + MultiSelect +} from '@/components/ui/multi-select' +import { FormProps } from '@/pages/Contribution/types' +import { Baby, Stethoscope, Coins, BookMarked, PawPrint, Trees, Church, Microscope, Globe, Accessibility, Scale, Shuffle, Brush } from 'lucide-react' +import InputError from '@/components/shared/InputError' +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { usePage } from '@inertiajs/react' +import KeywordAsyncCreatableSelect from '@/components/pages/contribution/new/OsblDatasheet/KeywordAsyncCreatableSelect' +import HelpTooltip from '@/components/shared/HelpTooltip' +import InterventionAreaAsyncCreatableSelect from '@/components/pages/contribution/new/OsblDatasheet/InterventionAreaAsyncCreatableSelect' +import MyNumberInput from '@/components/shared/MyNumberInput' +import MyCheckbox from '@/components/shared/MyCheckbox' + +const CausesList = [ + { value: 'environnement', label: 'Environnement', icon: Trees }, + { value: 'protection-de-l-enfance', label: 'Protection de l\'enfance', icon: Baby }, + { value: 'sante', label: 'Santé', icon: Stethoscope }, + { value: 'lutte-contre-la-précarité', label: 'Lutte contre la précarité', icon: Coins }, + { value: 'education', label: 'Éducation', icon: BookMarked }, + { value: 'protection-animale', label: 'Protection animale', icon: PawPrint }, + { value: 'recherche', label: 'Recherche', icon: Microscope }, + { value: 'arts-culture-patrimoine', label: 'Arts, Culture, Patrimoine', icon: Brush }, + { value: 'aide-internationale', label: 'Aide internationale', icon: Globe }, + { value: 'handicap', label: 'Handicap', icon: Accessibility }, + { value: 'justice-sociale', label: 'Justice sociale', icon: Scale }, + { value: 'religion', label: 'Religion', icon: Church }, + { value: 'autre', label: 'Autre', icon: Shuffle } +] + +const GeographicalScaleList = [ + { value: 'local', label: 'Locale' }, + { value: 'regional', label: 'Régionale' }, + { value: 'national', label: 'Nationale' }, + { value: 'international', label: 'Internationale' } +] + +const OsblTypeList = [ + { value: 'association', label: 'Association' }, + { value: 'fonds_de_dotation', label: 'Fonds de dotation' }, + { value: 'fondation', label: 'Fondation' } +] + +export default function OsblDatasheet ({ data, setData, errors, clearErrors }: Omit): ReactElement { + const causes = usePage().props.causes as Record + const labels = usePage().props.labels as Array<{ value: string, label: string }> + + const syncedCausesList = CausesList.map(cause => ({ + ...cause, + value: String(causes[cause.value]) + })) + + return ( +
+

Fiche technique

+ +
+
+ + { + setData('osbls_causes_attributes', value.map(cause => ({ cause_id: cause }))) + clearErrors('osbls_causes_attributes') + }} + placeholder='' + variant='secondary' + animation={0} + maxCount={1} + className={errors.osbls_causes_attributes !== undefined ? 'border-red-600' : ''} + /> + {Boolean(errors.osbls_causes_attributes) && {errors.osbls_causes_attributes}} +
+ +
+ + { + setData('tax_reduction', value) + clearErrors('tax_reduction') + }} + > +
+ + +
+
+ + +
+
+ {Boolean(errors.tax_reduction) && {errors.tax_reduction}} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + {data.osbl_type === 'association' && ( + setData('public_utility', checked)} + > + Reconnue d'utilité publique (ARUP) + + )} +
+ + { + setData('creation_year', e.target.value) + }} + /> + +
+ + { + setData('osbls_labels_attributes', value.map(label => ({ label_id: label }))) + clearErrors('osbls_labels_attributes') + }} + placeholder='' + variant='default' + animation={0} + maxCount={2} + /> +
+
+
+ ) +} diff --git a/app/frontend/components/pages/contribution/new/InterventionAreaAsyncCreatableSelect.tsx b/app/frontend/components/pages/contribution/new/OsblDatasheet/InterventionAreaAsyncCreatableSelect.tsx similarity index 100% rename from app/frontend/components/pages/contribution/new/InterventionAreaAsyncCreatableSelect.tsx rename to app/frontend/components/pages/contribution/new/OsblDatasheet/InterventionAreaAsyncCreatableSelect.tsx diff --git a/app/frontend/components/pages/contribution/new/KeywordAsyncCreatableSelect.tsx b/app/frontend/components/pages/contribution/new/OsblDatasheet/KeywordAsyncCreatableSelect.tsx similarity index 100% rename from app/frontend/components/pages/contribution/new/KeywordAsyncCreatableSelect.tsx rename to app/frontend/components/pages/contribution/new/OsblDatasheet/KeywordAsyncCreatableSelect.tsx diff --git a/app/frontend/components/pages/contribution/new/OsblDocuments.tsx b/app/frontend/components/pages/contribution/new/OsblDocuments.tsx index 2273e8c..bb29cc6 100644 --- a/app/frontend/components/pages/contribution/new/OsblDocuments.tsx +++ b/app/frontend/components/pages/contribution/new/OsblDocuments.tsx @@ -1,198 +1,97 @@ import { ReactElement } from 'react' -import { FormProps, DocumentRecord } from '@/pages/Contribution/types' +import { FormProps, Document } from '@/pages/Contribution/types' import { Button } from '@/components/ui/button' -import { PlusIcon, TrashIcon, ChevronDown } from 'lucide-react' -import MyInput from '@/components/forms/MyInput' +import { PlusIcon, TrashIcon, PencilIcon } from 'lucide-react' import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue -} from '@/components/ui/select' -import MyNumberInput from '@/components/forms/MyNumberInput' -import { Textarea } from '@/components/ui/textarea' -import { Card, CardContent, CardHeader } from '@/components/ui/card' -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' -import MyFileInput from '@/components/shared/MyFileInput' + Sheet, + SheetTrigger +} from '@/components/ui/sheet' +import OsblDocumentSheet from '@/components/pages/contribution/new/OsblDocuments/OsblDocumentSheet' -const DocumentTypeList = [ - { value: 'statuts', label: 'Statuts' }, - { value: 'rapport_activite', label: 'Rapport d\'activité' }, - { value: 'rapport_financier', label: 'Rapport financier' }, - { value: 'proces_verbal', label: 'Procès verbal' }, - { value: 'autre', label: 'Autre' } -] - -export default function OsblDocuments ({ data, setData, errors, clearErrors }: FormProps): ReactElement { +export default function OsblDocuments ({ data, setData, errors, clearErrors, setError }: FormProps): ReactElement { const documents = data.document_attachments_attributes ?? [] - function handleDocumentChange ( - index: number, - field: keyof DocumentRecord, - value: DocumentRecord[typeof field] - ): void { - const updatedDocuments = documents.map((doc, i: number) => - i === index - ? { - ...doc, - document_attributes: { - ...(doc.document_attributes ?? {}), - [field]: value - } - } - : doc - ) + function addDocument (document: Document, index: number): void { + const updatedDocuments = [...documents] + updatedDocuments[index] = { document_attributes: document } setData('document_attachments_attributes', updatedDocuments) - - if (field === 'file' && value instanceof File) { - clearErrors(`document_attachments_attributes.${index}.document_attributes.file`) - } } - function handleAdd (e: React.MouseEvent): void { - e.preventDefault() - setData('document_attachments_attributes', [...documents, { document_attributes: {} }]) + function handleDocumentRemove (e: React.MouseEvent, index: number): void { + e.preventDefault() // prevent the form from being submitted + setData('document_attachments_attributes', documents.filter((_, i) => i !== index)) } - function handleRemove ( - e: React.MouseEvent, - index: number - ): void { - e.preventDefault() - const updatedDocuments = documents.filter((_, i) => i !== index) - setData('document_attachments_attributes', updatedDocuments) + function getDocumentDisplayName (document: Document): string { + if (document.name !== undefined) { + return document.name + } + + if (["Rapport d'activité", 'Rapport financier'].includes(document.type)) { + return `${document.type} ${document.year as number}` + } + + return document.type } return ( -
-
-
-

Documents

- -
+
+
+

Documents

+ + + + + addDocument(doc, documents.length)} + errors={errors} + clearErrors={clearErrors} + setError={setError} + /> + +
+ {documents.length > 0 && (
- {documents?.map((doc, index) => ( - - -
- - + {documents.map((doc, index) => ( + +
+

{getDocumentDisplayName(doc.document_attributes)}

+
+ + +
- - - - {['rapport_activite', 'rapport_financier'].includes(doc.document_attributes?.type ?? '') && ( - handleDocumentChange(index, 'year', e.target.value)} - placeholder='Année *' - required - /> - )} - - {['proces_verbal', 'autre'].includes(doc.document_attributes?.type ?? '') && ( - handleDocumentChange(index, 'name', e.target.value)} - required - /> - )} - - handleDocumentChange(index, 'file', e.target.files?.[0])} - accept='.pdf' - required - error={errors[`document_attachments_attributes.${index}.document_attributes.file`]} - /> - - - - Informations additionnelles - - - - {!['proces_verbal', 'autre'].includes(doc.document_attributes?.type ?? '') && ( - handleDocumentChange(index, 'name', e.target.value)} - placeholder='Nom du document' - /> - )} - - {!['rapport_activite', 'rapport_financier'].includes(doc.document_attributes?.type ?? '') && ( - handleDocumentChange(index, 'year', e.target.value)} - placeholder='Année' - /> - )} - -
-