mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
feat: integra sistema de idiomas com banco de dados
Some checks failed
Docker Build and Push / build (push) Has been cancelled
Some checks failed
Docker Build and Push / build (push) Has been cancelled
- Integra completamente com a tabela languages - Adiciona suporte para ícones de bandeira e instruções - Remove LANGUAGE_OPTIONS hard coded - Usa DEFAULT_LANGUAGE do type - Melhora validações e UX do seletor de idiomas - Atualiza CHANGELOG.md para versão 1.4.0
This commit is contained in:
parent
ba93f3ef29
commit
abe4ce86d4
@ -5,6 +5,7 @@ import { useSession } from '../../hooks/useSession';
|
||||
import { useStoryCategories } from '../../hooks/useStoryCategories';
|
||||
import { Wand2, ArrowLeft, Globe } from 'lucide-react';
|
||||
import { useStudentTracking } from '../../hooks/useStudentTracking';
|
||||
import { useLanguages } from '../../hooks/useLanguages';
|
||||
|
||||
interface Category {
|
||||
id: string;
|
||||
@ -44,12 +45,6 @@ interface StoryGeneratorProps {
|
||||
setChoices: React.Dispatch<React.SetStateAction<StoryChoices>>;
|
||||
}
|
||||
|
||||
const LANGUAGE_OPTIONS = [
|
||||
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
||||
{ value: 'en-US', label: 'Inglês (EUA)' },
|
||||
{ value: 'es-ES', label: 'Espanhol (Espanha)' }
|
||||
] as const;
|
||||
|
||||
export function StoryGenerator({
|
||||
initialContext = '',
|
||||
onContextChange,
|
||||
@ -62,7 +57,8 @@ export function StoryGenerator({
|
||||
choices,
|
||||
setChoices
|
||||
}: StoryGeneratorProps) {
|
||||
const { themes, subjects, characters, settings, isLoading } = useStoryCategories();
|
||||
const { themes, subjects, characters, settings, isLoading: isCategoriesLoading } = useStoryCategories();
|
||||
const { languages, supportedLanguages, isLoading: isLanguagesLoading } = useLanguages();
|
||||
|
||||
// Definir steps com os dados obtidos
|
||||
const steps: StoryStep[] = [
|
||||
@ -173,7 +169,8 @@ export function StoryGenerator({
|
||||
const handleLanguageSelect = (language: string) => {
|
||||
console.log('Selecionando idioma:', language);
|
||||
|
||||
if (!LANGUAGE_OPTIONS.some(opt => opt.value === language)) {
|
||||
const selectedLanguage = languages.find(lang => lang.code === language);
|
||||
if (!selectedLanguage) {
|
||||
setError('Idioma inválido selecionado');
|
||||
return;
|
||||
}
|
||||
@ -252,7 +249,7 @@ export function StoryGenerator({
|
||||
}
|
||||
|
||||
// Validar idioma
|
||||
if (!choices.language_type || !LANGUAGE_OPTIONS.some(opt => opt.value === choices.language_type)) {
|
||||
if (!choices.language_type || !languages.some(lang => lang.code === choices.language_type)) {
|
||||
setError('Idioma não selecionado ou inválido');
|
||||
return;
|
||||
}
|
||||
@ -473,7 +470,7 @@ export function StoryGenerator({
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
if (isCategoriesLoading || isLanguagesLoading) {
|
||||
return (
|
||||
<div className="animate-pulse space-y-8">
|
||||
<div className="h-2 bg-gray-200 rounded-full" />
|
||||
@ -506,27 +503,38 @@ export function StoryGenerator({
|
||||
|
||||
{currentStep.isLanguageStep ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{LANGUAGE_OPTIONS.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => handleLanguageSelect(option.value)}
|
||||
className={`p-6 rounded-xl border-2 transition-all text-left ${
|
||||
choices.language_type === option.value
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-200 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="h-6 w-6 text-purple-600" />
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{option.label}</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
Escreva sua história em {option.label}
|
||||
</p>
|
||||
{supportedLanguages.map((option) => {
|
||||
const languageDetails = languages.find(lang => lang.code === option.value);
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
onClick={() => handleLanguageSelect(option.value)}
|
||||
className={`p-6 rounded-xl border-2 transition-all text-left ${
|
||||
choices.language_type === option.value
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-200 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{languageDetails?.flag_icon ? (
|
||||
<img
|
||||
src={languageDetails.flag_icon}
|
||||
alt={`Bandeira ${option.label}`}
|
||||
className="h-6 w-6 object-cover rounded-full"
|
||||
/>
|
||||
) : (
|
||||
<Globe className="h-6 w-6 text-purple-600" />
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">{option.label}</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
{`Escreva sua história em ${option.label}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : currentStep.isContextStep ? (
|
||||
<div className="space-y-4">
|
||||
|
||||
44
src/hooks/useLanguages.ts
Normal file
44
src/hooks/useLanguages.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import type { Language, SupportedLanguageCode, LanguageOption } from '../types/language';
|
||||
|
||||
interface UseLanguagesReturn {
|
||||
languages: Language[];
|
||||
isLoading: boolean;
|
||||
error: Error | null;
|
||||
getLanguageLabel: (code: SupportedLanguageCode) => string;
|
||||
supportedLanguages: LanguageOption[];
|
||||
}
|
||||
|
||||
export function useLanguages(): UseLanguagesReturn {
|
||||
const { data: languages = [], isLoading, error } = useQuery<Language[]>({
|
||||
queryKey: ['languages'],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('languages')
|
||||
.select('*')
|
||||
.order('name');
|
||||
|
||||
if (error) throw error;
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
const supportedLanguages: LanguageOption[] = languages.map(lang => ({
|
||||
value: lang.code as SupportedLanguageCode,
|
||||
label: lang.name
|
||||
}));
|
||||
|
||||
const getLanguageLabel = (code: SupportedLanguageCode): string => {
|
||||
const language = languages.find(lang => lang.code === code);
|
||||
return language?.name || code;
|
||||
};
|
||||
|
||||
return {
|
||||
languages,
|
||||
isLoading,
|
||||
error: error as Error | null,
|
||||
getLanguageLabel,
|
||||
supportedLanguages
|
||||
};
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ArrowLeft, Sparkles, Globe } from 'lucide-react';
|
||||
import { ArrowLeft, Sparkles } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { StoryGenerator } from '../../components/story/StoryGenerator';
|
||||
import { useSession } from '../../hooks/useSession';
|
||||
@ -9,12 +9,7 @@ import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
||||
import { useSpeechRecognition } from '@/features/voice-commands/hooks/useSpeechRecognition';
|
||||
import { VoiceCommandButton } from '@/features/voice-commands/components/VoiceCommandButton';
|
||||
import type { StoryChoices } from '@/components/story/StoryGenerator';
|
||||
|
||||
const LANGUAGE_OPTIONS = [
|
||||
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
||||
{ value: 'en-US', label: 'Inglês (EUA)' },
|
||||
{ value: 'es-ES', label: 'Espanhol (Espanha)' }
|
||||
] as const;
|
||||
import { DEFAULT_LANGUAGE } from '@/types/language';
|
||||
|
||||
export function CreateStoryPage() {
|
||||
const navigate = useNavigate();
|
||||
@ -45,7 +40,7 @@ export function CreateStoryPage() {
|
||||
character_id: null,
|
||||
setting_id: null,
|
||||
context: '',
|
||||
language_type: 'pt-BR'
|
||||
language_type: DEFAULT_LANGUAGE
|
||||
});
|
||||
|
||||
// Manipuladores para gravação de voz
|
||||
|
||||
38
src/types/language.ts
Normal file
38
src/types/language.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { supabase } from "@/lib/supabase";
|
||||
|
||||
export interface Language {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
instructions: string | null;
|
||||
flag_icon: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// Busca as opções de idioma do banco de dados
|
||||
export const getLanguageOptions = async () => {
|
||||
const { data: languages, error } = await supabase
|
||||
.from('languages')
|
||||
.select('code, name')
|
||||
.order('name');
|
||||
|
||||
if (error) {
|
||||
console.error('Erro ao buscar idiomas:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
return languages.map(lang => ({
|
||||
value: lang.code,
|
||||
label: lang.name
|
||||
}));
|
||||
};
|
||||
|
||||
export interface LanguageOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type SupportedLanguageCode = string;
|
||||
|
||||
export const DEFAULT_LANGUAGE = 'pt-BR';
|
||||
@ -87,6 +87,8 @@
|
||||
| public | student_phonics_achievements | student_id | FOREIGN KEY |
|
||||
| public | phonics_word_audio | id | PRIMARY KEY |
|
||||
| public | phonics_word_audio | word | UNIQUE |
|
||||
| public | languages | code | UNIQUE |
|
||||
| public | languages | id | PRIMARY KEY |
|
||||
| public | story_recordings | story_id | FOREIGN KEY |
|
||||
| public | story_recordings | story_id | FOREIGN KEY |
|
||||
| public | story_recordings | | CHECK |
|
||||
@ -291,6 +293,11 @@
|
||||
| public | phonics_word_audio | | CHECK |
|
||||
| public | phonics_word_audio | | CHECK |
|
||||
| public | phonics_word_audio | | CHECK |
|
||||
| public | languages | | CHECK |
|
||||
| public | languages | | CHECK |
|
||||
| public | languages | | CHECK |
|
||||
| public | languages | | CHECK |
|
||||
| public | languages | | CHECK |
|
||||
| public | story_recordings | | CHECK |
|
||||
| public | story_recordings | | CHECK |
|
||||
| public | story_recordings | | CHECK |
|
||||
|
||||
@ -72,8 +72,8 @@
|
||||
| realtime | subscription | claims | jsonb | NO | |
|
||||
| realtime | messages | inserted_at | timestamp without time zone | NO | now() |
|
||||
| public | phonics_categories | level | integer | NO | |
|
||||
| storage | objects | bucket_id | text | YES | |
|
||||
| public | story_characters | slug | text | NO | |
|
||||
| storage | objects | bucket_id | text | YES | |
|
||||
| public | students | birth_date | date | YES | |
|
||||
| auth | refresh_tokens | revoked | boolean | YES | |
|
||||
| storage | s3_multipart_uploads | owner_id | text | YES | |
|
||||
@ -89,6 +89,7 @@
|
||||
| public | phonics_words | created_at | timestamp with time zone | YES | CURRENT_TIMESTAMP |
|
||||
| public | teacher_invites | expires_at | timestamp with time zone | NO | |
|
||||
| auth | identities | provider_id | text | NO | |
|
||||
| public | languages | name | character varying | NO | |
|
||||
| net | _http_response | headers | jsonb | YES | |
|
||||
| auth | mfa_challenges | otp_code | text | YES | |
|
||||
| public | stories | character_id | uuid | YES | |
|
||||
@ -259,8 +260,8 @@
|
||||
| public | story_settings | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| public | story_subjects | description | text | NO | |
|
||||
| public | stories | content | jsonb | NO | |
|
||||
| auth | flow_state | provider_access_token | text | YES | |
|
||||
| vault | secrets | secret | text | NO | |
|
||||
| auth | flow_state | provider_access_token | text | YES | |
|
||||
| public | interests | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| public | teachers | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| storage | s3_multipart_uploads | created_at | timestamp with time zone | NO | now() |
|
||||
@ -325,12 +326,13 @@
|
||||
| extensions | pg_stat_statements | blk_read_time | double precision | YES | |
|
||||
| public | phonics_exercises | title | character varying | NO | |
|
||||
| auth | saml_providers | id | uuid | NO | |
|
||||
| public | languages | flag_icon | character varying | YES | |
|
||||
| auth | sessions | user_agent | text | YES | |
|
||||
| public | story_recordings | processed_at | timestamp with time zone | YES | |
|
||||
| storage | s3_multipart_uploads_parts | bucket_id | text | NO | |
|
||||
| pgsodium | decrypted_key | name | text | YES | |
|
||||
| public | phonics_words | word | character varying | NO | |
|
||||
| public | teachers | name | text | NO | |
|
||||
| public | phonics_words | word | character varying | NO | |
|
||||
| auth | sso_providers | created_at | timestamp with time zone | YES | |
|
||||
| storage | buckets | file_size_limit | bigint | YES | |
|
||||
| auth | sso_domains | sso_provider_id | uuid | NO | |
|
||||
@ -406,6 +408,7 @@
|
||||
| storage | s3_multipart_uploads_parts | key | text | NO | |
|
||||
| public | story_details | updated_at | timestamp with time zone | YES | |
|
||||
| auth | mfa_challenges | factor_id | uuid | NO | |
|
||||
| public | languages | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| pgsodium | masking_rule | attrelid | oid | YES | |
|
||||
| public | students | name | text | NO | |
|
||||
| storage | s3_multipart_uploads_parts | owner_id | text | YES | |
|
||||
@ -415,8 +418,8 @@
|
||||
| public | story_details | subject_icon | text | YES | |
|
||||
| auth | schema_migrations | version | character varying | NO | |
|
||||
| public | phonics_word_audio | audio_path | text | NO | |
|
||||
| auth | one_time_tokens | token_hash | text | NO | |
|
||||
| public | story_settings | slug | text | NO | |
|
||||
| auth | one_time_tokens | token_hash | text | NO | |
|
||||
| public | story_recordings | transcription | text | YES | |
|
||||
| storage | objects | metadata | jsonb | YES | |
|
||||
| public | story_characters | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
@ -436,8 +439,8 @@
|
||||
| public | story_details | content | jsonb | YES | |
|
||||
| public | teachers | class_ids | ARRAY | YES | |
|
||||
| extensions | pg_stat_statements | shared_blks_hit | bigint | YES | |
|
||||
| public | teacher_classes | class_id | uuid | NO | |
|
||||
| public | story_generations | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | teacher_classes | class_id | uuid | NO | |
|
||||
| auth | flow_state | auth_code_issued_at | timestamp with time zone | YES | |
|
||||
| public | media_types | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | students | avatar_url | text | YES | |
|
||||
@ -473,8 +476,8 @@
|
||||
| auth | mfa_factors | user_id | uuid | NO | |
|
||||
| public | phonics_word_audio | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | students | guardian_phone | text | YES | |
|
||||
| pgsodium | masking_rule | relname | name | YES | |
|
||||
| storage | s3_multipart_uploads | key | text | NO | |
|
||||
| pgsodium | masking_rule | relname | name | YES | |
|
||||
| auth | sessions | ip | inet | YES | |
|
||||
| auth | refresh_tokens | updated_at | timestamp with time zone | YES | |
|
||||
| public | story_recordings | pronunciation_score | integer | YES | |
|
||||
@ -486,8 +489,8 @@
|
||||
| pgsodium | key | key_context | bytea | YES | '\x7067736f6469756d'::bytea |
|
||||
| public | story_settings | icon | text | NO | |
|
||||
| public | story_generations | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| storage | objects | name | text | YES | |
|
||||
| public | story_characters | title | text | NO | |
|
||||
| storage | objects | name | text | YES | |
|
||||
| public | student_achievements | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | story_exercise_words | syllable_pattern | text | YES | |
|
||||
| auth | flow_state | code_challenge | text | NO | |
|
||||
@ -501,6 +504,7 @@
|
||||
| public | student_phonics_achievements | student_id | uuid | YES | |
|
||||
| public | teachers | phone | text | YES | |
|
||||
| vault | decrypted_secrets | name | text | YES | |
|
||||
| public | languages | code | character varying | NO | |
|
||||
| auth | instances | id | uuid | NO | |
|
||||
| public | story_pages | text | text | NO | |
|
||||
| pgsodium | key | associated_data | text | YES | 'associated'::text |
|
||||
@ -551,6 +555,7 @@
|
||||
| pgsodium | decrypted_key | decrypted_raw_key | bytea | YES | |
|
||||
| auth | instances | created_at | timestamp with time zone | YES | |
|
||||
| storage | migrations | hash | character varying | NO | |
|
||||
| public | languages | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) |
|
||||
| public | story_recordings | error_message | text | YES | |
|
||||
| public | story_pages | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | student_phonics_attempts | created_at | timestamp with time zone | YES | CURRENT_TIMESTAMP |
|
||||
@ -582,6 +587,7 @@
|
||||
| public | story_pages | page_number | integer | NO | |
|
||||
| extensions | pg_stat_statements | temp_blk_read_time | double precision | YES | |
|
||||
| net | _http_response | status_code | integer | YES | |
|
||||
| public | languages | instructions | text | YES | |
|
||||
| public | phonics_exercises | description | text | YES | |
|
||||
| auth | saml_relay_states | request_id | text | NO | |
|
||||
| storage | s3_multipart_uploads_parts | part_number | integer | NO | |
|
||||
@ -603,6 +609,7 @@
|
||||
| pgsodium | masking_rule | key_id_column | text | YES | |
|
||||
| storage | s3_multipart_uploads_parts | id | uuid | NO | gen_random_uuid() |
|
||||
| auth | identities | user_id | uuid | NO | |
|
||||
| public | languages | id | uuid | NO | uuid_generate_v4() |
|
||||
| public | story_pages | image_path | text | YES | |
|
||||
| auth | saml_providers | metadata_url | text | YES | |
|
||||
| auth | instances | raw_base_config | text | YES | |
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
| 65876 | public | interests | Students can insert their own interests | a | | (auth.uid() = student_id) |
|
||||
| 65877 | public | interests | Students can update their own interests | w | (auth.uid() = student_id) | (auth.uid() = student_id) |
|
||||
| 65875 | public | interests | Students can view their own interests | r | (auth.uid() = student_id) | |
|
||||
| 104599 | public | languages | Allow insert/update for admins only | * | ((auth.jwt() ->> 'role'::text) = 'admin'::text) | ((auth.jwt() ->> 'role'::text) = 'admin'::text) |
|
||||
| 104598 | public | languages | Allow read access for all authenticated users | r | true | |
|
||||
| 79931 | public | phonics_categories | Permitir leitura de categorias fonéticas para usuários autent | r | true | |
|
||||
| 79932 | public | phonics_exercise_types | Permitir leitura de tipos de exercícios fonéticos para usuár | r | true | |
|
||||
| 79934 | public | phonics_exercise_words | Permitir leitura de relações exercício-palavra para usuário | r | true | |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user