feat: add stock management and form resets while removing deprecated barber_ids and updating UI styles

This commit is contained in:
2026-05-13 10:30:40 +01:00
parent 42995acd8e
commit 916e068ebe
3 changed files with 131 additions and 69 deletions

View File

@@ -388,7 +388,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
name: service.name,
price: service.price,
duration: service.duration,
barber_ids: service.barberIds || [],
// Removed barber_ids as it doesn't exist in the current schema
}]);
if (error) {
console.error('Erro addService:', error);
@@ -402,7 +402,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
name: service.name,
price: service.price,
duration: service.duration,
barber_ids: service.barberIds || [],
}).eq('id', service.id);
await refreshShops();
};

View File

@@ -46,6 +46,13 @@ export default function Dashboard() {
const [activeTab, setActiveTab] = useState<Tab>('agenda');
const [loading, setLoading] = useState(false);
// Clear forms on tab change
React.useEffect(() => {
setFormSvc({ name: '', price: '', duration: '' });
setFormProd({ name: '', price: '', stock: '' });
setFormBarb({ name: '', specialties: '' });
}, [activeTab]);
// Agenda Filters
const [dateFilter, setDateFilter] = useState(new Date().toISOString().split('T')[0]);
const shopAppointments = useMemo(() => {
@@ -218,13 +225,13 @@ export default function Dashboard() {
<Text style={styles.ticketMeta}>Profissional: {barb?.name}</Text>
<View style={styles.ticketActions}>
{appt.status === 'pendente' && (
<TouchableOpacity onPress={() => updateAppointmentStatus(appt.id, 'confirmado')} style={styles.confirmBtn}>
<Text style={styles.confirmBtnTxt}>Confirmar</Text>
<TouchableOpacity onPress={() => updateAppointmentStatus(appt.id, 'confirmado')} style={styles.premiumActionBtn}>
<Text style={styles.premiumActionBtnTxt}>Confirmar</Text>
</TouchableOpacity>
)}
{appt.status !== 'cancelado' && appt.status !== 'concluido' && (
<TouchableOpacity onPress={() => updateAppointmentStatus(appt.id, 'cancelado')} style={styles.cancelBtn}>
<Text style={styles.cancelBtnTxt}>Cancelar</Text>
<TouchableOpacity onPress={() => updateAppointmentStatus(appt.id, 'cancelado')} style={[styles.premiumActionBtn, { backgroundColor: 'rgba(239,68,68,0.1)' }]}>
<Text style={[styles.premiumActionBtnTxt, { color: '#ef4444' }]}>Cancelar</Text>
</TouchableOpacity>
)}
</View>
@@ -282,7 +289,22 @@ export default function Dashboard() {
<Card key={p.id} style={styles.itemCard}>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>{p.name}</Text>
<Text style={styles.itemMeta}>Stock: {p.stock} · {currency(p.price)}</Text>
<Text style={styles.itemMeta}>{currency(p.price)}</Text>
</View>
<View style={styles.stockControl}>
<TouchableOpacity
onPress={() => supabase.from('products').update({ stock: Math.max(0, p.stock - 1) }).eq('id', p.id).then(() => refreshShops())}
style={styles.stockBtn}
>
<Text style={styles.stockBtnTxt}>-</Text>
</TouchableOpacity>
<Text style={styles.stockVal}>{p.stock}</Text>
<TouchableOpacity
onPress={() => supabase.from('products').update({ stock: p.stock + 1 }).eq('id', p.id).then(() => refreshShops())}
style={styles.stockBtn}
>
<Text style={styles.stockBtnTxt}>+</Text>
</TouchableOpacity>
</View>
<TouchableOpacity onPress={() => deleteProduct(shop.id, p.id)} style={styles.deleteIcon}>
<Text style={styles.deleteTxt}></Text>
@@ -562,29 +584,20 @@ const styles = StyleSheet.create({
gap: 12,
marginTop: 12,
},
confirmBtn: {
backgroundColor: 'rgba(16,185,129,0.15)',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 8,
premiumActionBtn: {
backgroundColor: 'rgba(16,185,129,0.1)',
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 12,
borderWidth: 1,
borderColor: 'rgba(16,185,129,0.2)',
},
confirmBtnTxt: {
premiumActionBtnTxt: {
color: '#10b981',
fontSize: 11,
fontWeight: '800',
textTransform: 'uppercase',
},
cancelBtn: {
backgroundColor: 'rgba(239,68,68,0.15)',
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 8,
},
cancelBtnTxt: {
color: '#ef4444',
fontSize: 11,
fontSize: 12,
fontWeight: '800',
textTransform: 'uppercase',
letterSpacing: 0.5,
},
emptyBox: {
alignItems: 'center',
@@ -605,6 +618,11 @@ const styles = StyleSheet.create({
alignItems: 'center',
gap: 14,
padding: 14,
backgroundColor: '#141420',
borderRadius: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.03)',
},
itemInfo: {
flex: 1,
@@ -618,6 +636,35 @@ const styles = StyleSheet.create({
color: '#64748b',
fontSize: 13,
},
stockControl: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#1c1c2e',
borderRadius: 12,
padding: 4,
gap: 12,
marginRight: 10,
},
stockBtn: {
width: 28,
height: 28,
borderRadius: 8,
backgroundColor: '#252538',
alignItems: 'center',
justifyContent: 'center',
},
stockBtnTxt: {
color: '#f8fafc',
fontSize: 16,
fontWeight: 'bold',
},
stockVal: {
color: '#f8fafc',
fontSize: 14,
fontWeight: '800',
minWidth: 20,
textAlign: 'center',
},
deleteIcon: {
width: 32,
height: 32,
@@ -636,6 +683,8 @@ const styles = StyleSheet.create({
gap: 12,
borderWidth: 1.5,
borderColor: 'rgba(99,102,241,0.2)',
borderRadius: 20,
marginTop: 10,
},
formTitle: {
color: '#f8fafc',

View File

@@ -127,7 +127,9 @@ export default function Profile() {
</View>
</View>
<TouchableOpacity onPress={logout} style={styles.logoutBtn}>
<Text style={styles.logoutIcon}></Text>
<View style={styles.logoutIconBg}>
<Text style={styles.logoutIcon}></Text>
</View>
</TouchableOpacity>
</View>
@@ -310,55 +312,67 @@ const styles = StyleSheet.create({
profileEmail: { color: '#64748b', fontSize: 14, fontWeight: '500' },
roleBadge: { alignSelf: 'flex-start', backgroundColor: 'rgba(99,102,241,0.1)', borderRadius: 8, paddingHorizontal: 8, paddingVertical: 3, marginTop: 4 },
roleText: { color: '#818cf8', fontSize: 10, fontWeight: '800', textTransform: 'uppercase' },
logoutBtn: { padding: 8 },
logoutIcon: { color: '#ef4444', fontSize: 24, fontWeight: '700' },
logoutBtn: {
padding: 4,
},
logoutIconBg: {
width: 40,
height: 40,
borderRadius: 12,
backgroundColor: 'rgba(239,68,68,0.1)',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: 'rgba(239,68,68,0.2)',
},
logoutIcon: { color: '#ef4444', fontSize: 20, fontWeight: '700' },
sectionTitle: { color: '#f8fafc', fontSize: 18, fontWeight: '800', marginBottom: 12 },
notifSection: { marginBottom: 32 },
notifScroll: { marginHorizontal: -20, paddingHorizontal: 20 },
notifCard: { backgroundColor: '#141420', width: 240, borderRadius: 16, padding: 14, marginRight: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', gap: 8 },
notifUnread: { borderColor: 'rgba(99,102,241,0.4)' },
notifMsg: { color: '#94a3b8', fontSize: 13, lineHeight: 18 },
markRead: { color: '#818cf8', fontSize: 12, fontWeight: '700' },
notifCard: { backgroundColor: '#141420', width: 240, borderRadius: 20, padding: 16, marginRight: 12, borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)', gap: 8, elevation: 2 },
notifUnread: { borderColor: 'rgba(99,102,241,0.3)', backgroundColor: '#18182b' },
notifMsg: { color: '#94a3b8', fontSize: 13, lineHeight: 18, fontWeight: '500' },
markRead: { color: '#818cf8', fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5 },
tabBar: { flexDirection: 'row', marginBottom: 24, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.06)' },
tabItem: { paddingVertical: 12, marginRight: 24, position: 'relative' },
tabItem: { paddingVertical: 14, marginRight: 24, position: 'relative' },
tabActive: {},
tabText: { color: '#475569', fontSize: 15, fontWeight: '700' },
tabTextActive: { color: '#a5b4fc' },
tabIndicator: { position: 'absolute', bottom: -1, left: 0, right: 0, height: 2, backgroundColor: '#6366f1', borderRadius: 2 },
tabTextActive: { color: '#f8fafc' },
tabIndicator: { position: 'absolute', bottom: -1, left: 0, right: 0, height: 3, backgroundColor: '#6366f1', borderRadius: 4 },
tabContent: { minHeight: 200 },
listContainer: { gap: 12 },
emptyTxt: { color: '#475569', textAlign: 'center', marginTop: 20 },
subTitle: { color: '#64748b', fontSize: 13, fontWeight: '700', textTransform: 'uppercase', marginBottom: 8 },
agendaCard: { padding: 14, gap: 12 },
agendaTop: { flexDirection: 'row', alignItems: 'center', gap: 12 },
dateBox: { backgroundColor: '#1c1c2e', padding: 8, borderRadius: 12, alignItems: 'center', minWidth: 50 },
dateDay: { color: '#f8fafc', fontSize: 18, fontWeight: '900' },
dateMonth: { color: '#6366f1', fontSize: 10, fontWeight: '800' },
agendaMain: { flex: 1, gap: 2 },
agendaShop: { color: '#f8fafc', fontSize: 16, fontWeight: '800' },
agendaTime: { color: '#64748b', fontSize: 13 },
statusTag: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 10 },
statusText: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase' },
reviewBtn: { marginTop: 4 },
shopCard: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 },
shopIcon: { width: 44, height: 44, borderRadius: 12, backgroundColor: 'rgba(255,255,255,0.03)', alignItems: 'center', justifyContent: 'center' },
shopIconText: { color: '#475569', fontSize: 18, fontWeight: '800' },
listContainer: { gap: 16 },
emptyTxt: { color: '#475569', textAlign: 'center', marginTop: 20, fontWeight: '600' },
subTitle: { color: '#64748b', fontSize: 13, fontWeight: '700', textTransform: 'uppercase', marginBottom: 8, letterSpacing: 1 },
agendaCard: { padding: 16, gap: 12, backgroundColor: '#141420', borderRadius: 20, borderWidth: 1, borderColor: 'rgba(255,255,255,0.03)' },
agendaTop: { flexDirection: 'row', alignItems: 'center', gap: 14 },
dateBox: { backgroundColor: '#1c1c2e', padding: 10, borderRadius: 14, alignItems: 'center', minWidth: 55, borderWidth: 1, borderColor: 'rgba(255,255,255,0.05)' },
dateDay: { color: '#f8fafc', fontSize: 20, fontWeight: '900' },
dateMonth: { color: '#818cf8', fontSize: 10, fontWeight: '800', textTransform: 'uppercase' },
agendaMain: { flex: 1, gap: 3 },
agendaShop: { color: '#f8fafc', fontSize: 17, fontWeight: '800' },
agendaTime: { color: '#64748b', fontSize: 13, fontWeight: '500' },
statusTag: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 10 },
statusText: { fontSize: 10, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 0.5 },
reviewBtn: { marginTop: 8, borderRadius: 12 },
shopCard: { flexDirection: 'row', alignItems: 'center', gap: 14, padding: 16, backgroundColor: '#141420', borderRadius: 20, marginBottom: 4 },
shopIcon: { width: 48, height: 48, borderRadius: 14, backgroundColor: 'rgba(99,102,241,0.05)', alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'rgba(99,102,241,0.1)' },
shopIconText: { color: '#818cf8', fontSize: 20, fontWeight: '900' },
shopName: { color: '#f8fafc', fontSize: 16, fontWeight: '800' },
shopAddr: { color: '#475569', fontSize: 12 },
shopRating: { color: '#fbbf24', fontWeight: '800', fontSize: 13 },
reviewCard: { padding: 16, gap: 8 },
reviewHeader: { flexDirection: 'row', justifyContent: 'space-between' },
reviewStars: { color: '#fbbf24', fontSize: 14 },
reviewDate: { color: '#475569', fontSize: 11 },
reviewComment: { color: '#f8fafc', fontSize: 13, lineHeight: 18 },
statCard: { padding: 20, alignItems: 'center', gap: 4 },
statLabel: { color: '#64748b', fontSize: 12, fontWeight: '700', textTransform: 'uppercase' },
statValue: { color: '#f8fafc', fontSize: 24, fontWeight: '900' },
reviewModal: { marginTop: 24, padding: 20, gap: 16, borderColor: 'rgba(99,102,241,0.3)' },
reviewTitle: { color: '#f8fafc', fontSize: 16, fontWeight: '800', textAlign: 'center' },
stars: { flexDirection: 'row', justifyContent: 'center', gap: 8 },
star: { fontSize: 32, color: '#1c1c2e' },
shopAddr: { color: '#475569', fontSize: 12, fontWeight: '500' },
shopRating: { color: '#fbbf24', fontWeight: '900', fontSize: 14 },
reviewCard: { padding: 18, gap: 10, backgroundColor: '#141420', borderRadius: 20 },
reviewHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
reviewStars: { color: '#fbbf24', fontSize: 15 },
reviewDate: { color: '#475569', fontSize: 12, fontWeight: '600' },
reviewComment: { color: '#f8fafc', fontSize: 14, lineHeight: 20, fontWeight: '400' },
statCard: { padding: 24, alignItems: 'center', gap: 6, backgroundColor: '#141420', borderRadius: 24, borderWidth: 1, borderColor: 'rgba(99,102,241,0.1)' },
statLabel: { color: '#64748b', fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1 },
statValue: { color: '#f8fafc', fontSize: 28, fontWeight: '900' },
reviewModal: { marginTop: 24, padding: 24, gap: 16, borderColor: 'rgba(99,102,241,0.3)', borderRadius: 24 },
reviewTitle: { color: '#f8fafc', fontSize: 18, fontWeight: '800', textAlign: 'center' },
stars: { flexDirection: 'row', justifyContent: 'center', gap: 12 },
star: { fontSize: 36, color: '#1c1c2e' },
starActive: { color: '#fbbf24' },
reviewInput: { backgroundColor: '#1c1c2e', borderRadius: 14, padding: 12, color: '#f8fafc', height: 80, textAlignVertical: 'top' },
reviewActions: { flexDirection: 'row', gap: 12 },
reviewInput: { backgroundColor: '#1c1c2e', borderRadius: 16, padding: 16, color: '#f8fafc', height: 100, textAlignVertical: 'top', borderWidth: 1, borderColor: 'rgba(255,255,255,0.05)' },
reviewActions: { flexDirection: 'row', gap: 14 },
});