Upload 10 files
Browse files- .gitignore +52 -0
- GITHUB_FILER.md +71 -0
- HUGGINGFACE_SETUP.md +211 -0
- MAPPSTRUKTUR.md +41 -0
- PROJEKTSTRUKTUR.md +96 -0
- README.md +251 -13
- START_HÄR.md +154 -0
- app.py +311 -0
- requirements.txt +4 -0
- secrets.toml.template +17 -0
.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Secrets - VIKTIGT: Lägg aldrig till secrets i Git!
|
| 2 |
+
.streamlit/secrets.toml
|
| 3 |
+
*.json
|
| 4 |
+
secrets/
|
| 5 |
+
credentials/
|
| 6 |
+
|
| 7 |
+
# Virtual environments
|
| 8 |
+
venv/
|
| 9 |
+
env/
|
| 10 |
+
ENV/
|
| 11 |
+
.venv
|
| 12 |
+
|
| 13 |
+
# Python
|
| 14 |
+
__pycache__/
|
| 15 |
+
*.py[cod]
|
| 16 |
+
*$py.class
|
| 17 |
+
*.so
|
| 18 |
+
.Python
|
| 19 |
+
build/
|
| 20 |
+
develop-eggs/
|
| 21 |
+
dist/
|
| 22 |
+
downloads/
|
| 23 |
+
eggs/
|
| 24 |
+
.eggs/
|
| 25 |
+
lib/
|
| 26 |
+
lib64/
|
| 27 |
+
parts/
|
| 28 |
+
sdist/
|
| 29 |
+
var/
|
| 30 |
+
wheels/
|
| 31 |
+
*.egg-info/
|
| 32 |
+
.installed.cfg
|
| 33 |
+
*.egg
|
| 34 |
+
|
| 35 |
+
# IDE
|
| 36 |
+
.vscode/
|
| 37 |
+
.idea/
|
| 38 |
+
*.swp
|
| 39 |
+
*.swo
|
| 40 |
+
*~
|
| 41 |
+
.DS_Store
|
| 42 |
+
|
| 43 |
+
# Logs
|
| 44 |
+
*.log
|
| 45 |
+
|
| 46 |
+
# Testing
|
| 47 |
+
.pytest_cache/
|
| 48 |
+
.coverage
|
| 49 |
+
htmlcov/
|
| 50 |
+
|
| 51 |
+
# Streamlit
|
| 52 |
+
.streamlit/config.toml
|
GITHUB_FILER.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📦 FILER FÖR GITHUB REPO
|
| 2 |
+
|
| 3 |
+
## ✅ Dessa 8 filer ska läggas i ditt GitHub repo:
|
| 4 |
+
|
| 5 |
+
1. **app.py** - Huvudapplikationen (11 KB)
|
| 6 |
+
2. **requirements.txt** - Python dependencies (68 bytes)
|
| 7 |
+
3. **.gitignore** - Skyddar dina secrets (476 bytes)
|
| 8 |
+
4. **README.md** - Fullständig dokumentation (6.6 KB)
|
| 9 |
+
5. **HUGGINGFACE_SETUP.md** - Guide för Hugging Face (5.8 KB)
|
| 10 |
+
6. **PROJEKTSTRUKTUR.md** - Översikt (2.3 KB)
|
| 11 |
+
7. **START_HÄR.md** - Snabbstart (4.5 KB)
|
| 12 |
+
8. **secrets.toml.template** - Mall för secrets (757 bytes)
|
| 13 |
+
|
| 14 |
+
**Total storlek:** ~31 KB
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## 🚀 Snabbkommando för Git:
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
# I din projektmapp:
|
| 22 |
+
git init
|
| 23 |
+
git add app.py requirements.txt .gitignore README.md HUGGINGFACE_SETUP.md PROJEKTSTRUKTUR.md START_HÄR.md secrets.toml.template
|
| 24 |
+
git commit -m "Initial commit: Områdesdata Manager"
|
| 25 |
+
git remote add origin https://github.com/DITT-ANVÄNDARNAMN/omradesdata-manager.git
|
| 26 |
+
git branch -M main
|
| 27 |
+
git push -u origin main
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## ❌ Lägg ALDRIG till dessa:
|
| 33 |
+
|
| 34 |
+
- `.streamlit/secrets.toml` (dina riktiga secrets)
|
| 35 |
+
- `*.json` (Service Account nycklar)
|
| 36 |
+
- `venv/` (virtuell miljö)
|
| 37 |
+
- `__pycache__/` (Python cache)
|
| 38 |
+
|
| 39 |
+
**.gitignore skyddar dig automatiskt!**
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## 📋 Verifiering:
|
| 44 |
+
|
| 45 |
+
Efter push till GitHub, kontrollera att:
|
| 46 |
+
|
| 47 |
+
✅ Alla 8 filer är synliga i ditt repo
|
| 48 |
+
✅ .gitignore fungerar (inga secrets synliga)
|
| 49 |
+
✅ README.md visas på repots startsida
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
## 🔗 Nästa steg efter GitHub push:
|
| 54 |
+
|
| 55 |
+
1. **Streamlit Cloud:** share.streamlit.io
|
| 56 |
+
- Välj ditt repo
|
| 57 |
+
- Lägg till secrets i web-interfacet
|
| 58 |
+
|
| 59 |
+
2. **Hugging Face:** huggingface.co/spaces
|
| 60 |
+
- Skapa Space med Streamlit SDK
|
| 61 |
+
- Lägg till GOOGLE_CREDENTIALS och SHEET_ID i secrets
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
## 💡 Tips:
|
| 66 |
+
|
| 67 |
+
- Gör repot **Private** om data är känslig
|
| 68 |
+
- Lägg till en LICENSE-fil om du vill
|
| 69 |
+
- Uppdatera README.md med dina egna anteckningar
|
| 70 |
+
|
| 71 |
+
Lycka till! 🎉
|
HUGGINGFACE_SETUP.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 Snabbstart: Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
## Steg-för-steg guide för att deploya på Hugging Face
|
| 4 |
+
|
| 5 |
+
### 1. Förbered Google Cloud (om inte redan gjort)
|
| 6 |
+
|
| 7 |
+
Se huvudinstruktioner i README.md för att:
|
| 8 |
+
- Skapa Google Cloud project
|
| 9 |
+
- Aktivera Google Sheets API och Google Drive API
|
| 10 |
+
- Skapa Service Account och ladda ner JSON-nyckel
|
| 11 |
+
- Dela ditt Google Sheet med service account-emailen
|
| 12 |
+
|
| 13 |
+
### 2. Skapa Hugging Face Space
|
| 14 |
+
|
| 15 |
+
1. Gå till [huggingface.co/spaces](https://huggingface.co/spaces)
|
| 16 |
+
2. Klicka **"Create new Space"**
|
| 17 |
+
3. Fyll i:
|
| 18 |
+
- **Owner**: Ditt användarnamn
|
| 19 |
+
- **Space name**: `omradesdata-manager` (eller valfritt namn)
|
| 20 |
+
- **License**: Apache 2.0 (eller valfri)
|
| 21 |
+
- **Select the Space SDK**: **Streamlit**
|
| 22 |
+
- **Space hardware**: CPU basic (gratis)
|
| 23 |
+
4. Klicka **"Create Space"**
|
| 24 |
+
|
| 25 |
+
### 3. Ladda upp filer
|
| 26 |
+
|
| 27 |
+
Du har två alternativ:
|
| 28 |
+
|
| 29 |
+
#### Alternativ A: Via Web Interface
|
| 30 |
+
|
| 31 |
+
1. I ditt nya Space, klicka **"Files"**
|
| 32 |
+
2. Klicka **"Add file"** → **"Upload files"**
|
| 33 |
+
3. Ladda upp:
|
| 34 |
+
- `app.py`
|
| 35 |
+
- `requirements.txt`
|
| 36 |
+
4. Klicka **"Commit changes"**
|
| 37 |
+
|
| 38 |
+
#### Alternativ B: Via Git (rekommenderas för framtida uppdateringar)
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
# Klona ditt Space
|
| 42 |
+
git clone https://huggingface.co/spaces/DITT-ANVÄNDARNAMN/omradesdata-manager
|
| 43 |
+
cd omradesdata-manager
|
| 44 |
+
|
| 45 |
+
# Kopiera filer
|
| 46 |
+
cp /sökväg/till/app.py .
|
| 47 |
+
cp /sökväg/till/requirements.txt .
|
| 48 |
+
|
| 49 |
+
# Commit och push
|
| 50 |
+
git add app.py requirements.txt
|
| 51 |
+
git commit -m "Initial commit"
|
| 52 |
+
git push
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### 4. Konfigurera Secrets
|
| 56 |
+
|
| 57 |
+
Detta är det **viktigaste steget**!
|
| 58 |
+
|
| 59 |
+
1. Gå till ditt Space
|
| 60 |
+
2. Klicka **"Settings"** (kugghjulsikonen)
|
| 61 |
+
3. Scrolla ner till **"Repository secrets"**
|
| 62 |
+
4. Klicka **"New secret"**
|
| 63 |
+
|
| 64 |
+
#### Lägg till Sheet ID:
|
| 65 |
+
|
| 66 |
+
- **Name**: `SHEET_ID`
|
| 67 |
+
- **Value**: `DIN_GOOGLE_SHEET_ID` (från URL:en)
|
| 68 |
+
- Klicka **"Save"**
|
| 69 |
+
|
| 70 |
+
#### Lägg till Google Credentials:
|
| 71 |
+
|
| 72 |
+
- **Name**: `GOOGLE_CREDENTIALS`
|
| 73 |
+
- **Value**: Hela innehållet från din JSON-nyckel (kopiera allt!)
|
| 74 |
+
- Klicka **"Save"**
|
| 75 |
+
|
| 76 |
+
### 5. Uppdatera app.py för Hugging Face
|
| 77 |
+
|
| 78 |
+
Lägg till dessa rader i början av `app.py` (efter imports):
|
| 79 |
+
|
| 80 |
+
```python
|
| 81 |
+
import json
|
| 82 |
+
import os
|
| 83 |
+
|
| 84 |
+
# För Hugging Face Spaces - läs secrets från environment
|
| 85 |
+
if 'GOOGLE_CREDENTIALS' in os.environ:
|
| 86 |
+
# Vi är på Hugging Face
|
| 87 |
+
google_creds = json.loads(os.environ['GOOGLE_CREDENTIALS'])
|
| 88 |
+
sheet_id = os.environ['SHEET_ID']
|
| 89 |
+
|
| 90 |
+
# Skapa fake secrets-struktur för kompatibilitet
|
| 91 |
+
if 'secrets' not in st.__dict__ or not hasattr(st, 'secrets'):
|
| 92 |
+
class Secrets:
|
| 93 |
+
def __init__(self):
|
| 94 |
+
self.data = {
|
| 95 |
+
'gcp_service_account': google_creds,
|
| 96 |
+
'sheet_id': sheet_id
|
| 97 |
+
}
|
| 98 |
+
def __getitem__(self, key):
|
| 99 |
+
return self.data[key]
|
| 100 |
+
|
| 101 |
+
st.secrets = Secrets()
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
**ELLER** använd denna uppdaterade `get_google_sheet()` funktion:
|
| 105 |
+
|
| 106 |
+
```python
|
| 107 |
+
@st.cache_resource
|
| 108 |
+
def get_google_sheet():
|
| 109 |
+
"""Anslut till Google Sheets"""
|
| 110 |
+
try:
|
| 111 |
+
# Försök först med Streamlit secrets (lokal utveckling)
|
| 112 |
+
if hasattr(st, 'secrets') and 'gcp_service_account' in st.secrets:
|
| 113 |
+
creds = Credentials.from_service_account_info(
|
| 114 |
+
st.secrets["gcp_service_account"],
|
| 115 |
+
scopes=SCOPES
|
| 116 |
+
)
|
| 117 |
+
sheet_id = st.secrets["sheet_id"]
|
| 118 |
+
# Annars använd environment variables (Hugging Face)
|
| 119 |
+
elif 'GOOGLE_CREDENTIALS' in os.environ:
|
| 120 |
+
google_creds = json.loads(os.environ['GOOGLE_CREDENTIALS'])
|
| 121 |
+
creds = Credentials.from_service_account_info(
|
| 122 |
+
google_creds,
|
| 123 |
+
scopes=SCOPES
|
| 124 |
+
)
|
| 125 |
+
sheet_id = os.environ['SHEET_ID']
|
| 126 |
+
else:
|
| 127 |
+
st.error("❌ Inga credentials hittades!")
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
client = gspread.authorize(creds)
|
| 131 |
+
sheet = client.open_by_key(sheet_id).sheet1
|
| 132 |
+
return sheet
|
| 133 |
+
except Exception as e:
|
| 134 |
+
st.error(f"❌ Kunde inte ansluta till Google Sheets: {e}")
|
| 135 |
+
return None
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### 6. Testa deployment
|
| 139 |
+
|
| 140 |
+
1. Space kommer automatiskt att bygga och deploya
|
| 141 |
+
2. Vänta 1-2 minuter
|
| 142 |
+
3. När status visar **"Running"**, klicka på din Space-URL
|
| 143 |
+
4. Testa att logga in med ett lösenord från ditt sheet!
|
| 144 |
+
|
| 145 |
+
### 7. Felsökning
|
| 146 |
+
|
| 147 |
+
#### "Kunde inte ansluta till Google Sheets"
|
| 148 |
+
|
| 149 |
+
**Kontrollera:**
|
| 150 |
+
- [ ] GOOGLE_CREDENTIALS är korrekt JSON (hela filen)
|
| 151 |
+
- [ ] SHEET_ID är rätt (från Google Sheets URL)
|
| 152 |
+
- [ ] Service account har tillgång till sheetet
|
| 153 |
+
- [ ] API:erna är aktiverade i Google Cloud
|
| 154 |
+
|
| 155 |
+
#### "No module named 'gspread'"
|
| 156 |
+
|
| 157 |
+
**Lösning:**
|
| 158 |
+
- Kontrollera att `requirements.txt` är uppladdad
|
| 159 |
+
- Rebuild Space (Settings → Factory reboot)
|
| 160 |
+
|
| 161 |
+
#### Space kraschar
|
| 162 |
+
|
| 163 |
+
**Kontrollera:**
|
| 164 |
+
- Logs i Space (klicka på "Logs")
|
| 165 |
+
- Syntax-fel i app.py
|
| 166 |
+
- Secrets är korrekt formaterade
|
| 167 |
+
|
| 168 |
+
### 8. Gör din Space privat (valfritt)
|
| 169 |
+
|
| 170 |
+
Om du vill ha en privat app:
|
| 171 |
+
|
| 172 |
+
1. Gå till **Settings**
|
| 173 |
+
2. Under **"Space visibility"**
|
| 174 |
+
3. Välj **"Private"**
|
| 175 |
+
4. Klicka **"Save"**
|
| 176 |
+
|
| 177 |
+
Nu kan bara du (och personer du delar länken med) se appen!
|
| 178 |
+
|
| 179 |
+
### 9. Uppdatera din app
|
| 180 |
+
|
| 181 |
+
För att göra ändringar i framtiden:
|
| 182 |
+
|
| 183 |
+
```bash
|
| 184 |
+
cd omradesdata-manager
|
| 185 |
+
# Gör dina ändringar i app.py
|
| 186 |
+
git add app.py
|
| 187 |
+
git commit -m "Uppdaterad funktion X"
|
| 188 |
+
git push
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
Space kommer automatiskt att rebuilda!
|
| 192 |
+
|
| 193 |
+
## 🎉 Klart!
|
| 194 |
+
|
| 195 |
+
Din app körs nu på Hugging Face Spaces! Dela länken med dina användare:
|
| 196 |
+
|
| 197 |
+
```
|
| 198 |
+
https://huggingface.co/spaces/DITT-ANVÄNDARNAMN/omradesdata-manager
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
## 💡 Pro Tips
|
| 202 |
+
|
| 203 |
+
1. **Snabbare builds**: Pin exakta versioner i requirements.txt
|
| 204 |
+
2. **Bättre prestanda**: Använd st.cache_resource för datahämtning
|
| 205 |
+
3. **Monitoring**: Kolla Logs regelbundet för fel
|
| 206 |
+
4. **Backups**: Gör regelbundna backups av ditt Google Sheet
|
| 207 |
+
|
| 208 |
+
## 📞 Behöver du hjälp?
|
| 209 |
+
|
| 210 |
+
- Hugging Face Community: [huggingface.co/discussions](https://huggingface.co/discussions)
|
| 211 |
+
- Streamlit Forum: [discuss.streamlit.io](https://discuss.streamlit.io)
|
MAPPSTRUKTUR.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📂 Mappstruktur för ditt projekt
|
| 2 |
+
|
| 3 |
+
```
|
| 4 |
+
omradesdata-manager/
|
| 5 |
+
│
|
| 6 |
+
├── 📄 app.py # Huvudapplikationen
|
| 7 |
+
├── 📄 requirements.txt # Python dependencies
|
| 8 |
+
├── 📄 .gitignore # Skyddar secrets från Git
|
| 9 |
+
├── 📄 README.md # Fullständig dokumentation
|
| 10 |
+
├── 📄 HUGGINGFACE_SETUP.md # Guide för Hugging Face
|
| 11 |
+
├── 📄 PROJEKTSTRUKTUR.md # Översikt över projektet
|
| 12 |
+
├── 📄 START_HÄR.md # Snabbstartguide
|
| 13 |
+
├── 📄 secrets.toml.template # Mall för secrets (utan riktiga värden)
|
| 14 |
+
│
|
| 15 |
+
└── 📁 .streamlit/ # ⚠️ SKAPA DENNA LOKALT (lägg INTE i Git!)
|
| 16 |
+
└── 📄 secrets.toml # Dina riktiga secrets (endast lokalt)
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
## 📦 Vad som ska i GitHub:
|
| 20 |
+
|
| 21 |
+
Allt UTOM `.streamlit/` mappen!
|
| 22 |
+
|
| 23 |
+
## 🏠 Vad som ska finnas lokalt:
|
| 24 |
+
|
| 25 |
+
Allt ovan + `.streamlit/secrets.toml` (för lokal testning)
|
| 26 |
+
|
| 27 |
+
## ☁️ Deployment-secrets:
|
| 28 |
+
|
| 29 |
+
- **Streamlit Cloud**: Lägg in manuellt i web-interfacet
|
| 30 |
+
- **Hugging Face**: Lägg in i Repository secrets
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 🎯 Snabb setup:
|
| 35 |
+
|
| 36 |
+
1. **Ladda ner** alla filer från ZIP-filen
|
| 37 |
+
2. **Packa upp** i en mapp (t.ex. `omradesdata-manager`)
|
| 38 |
+
3. **Skapa** `.streamlit/secrets.toml` lokalt (kopiera från template)
|
| 39 |
+
4. **Testa** lokalt: `streamlit run app.py`
|
| 40 |
+
5. **Pusha** till GitHub (utan .streamlit/)
|
| 41 |
+
6. **Deploya** på Streamlit Cloud eller Hugging Face
|
PROJEKTSTRUKTUR.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📁 Projektstruktur
|
| 2 |
+
|
| 3 |
+
```
|
| 4 |
+
omradesdata-manager/
|
| 5 |
+
│
|
| 6 |
+
├── app.py # Huvudapplikationen
|
| 7 |
+
├── requirements.txt # Python-dependencies
|
| 8 |
+
├── .gitignore # Git ignore-fil
|
| 9 |
+
├── README.md # Huvuddokumentation
|
| 10 |
+
├── HUGGINGFACE_SETUP.md # Guide för Hugging Face deployment
|
| 11 |
+
├── secrets.toml.template # Mall för secrets-fil
|
| 12 |
+
│
|
| 13 |
+
└── .streamlit/ # Skapas av dig
|
| 14 |
+
└── secrets.toml # Dina secrets (lägg INTE i Git!)
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## 📝 Filbeskrivningar
|
| 18 |
+
|
| 19 |
+
### app.py
|
| 20 |
+
Huvudapplikationen med all funktionalitet:
|
| 21 |
+
- Lösenordsautentisering
|
| 22 |
+
- Visa områdesdata
|
| 23 |
+
- Editera tillåtna fält
|
| 24 |
+
- Spara ändringar till Google Sheets
|
| 25 |
+
|
| 26 |
+
### requirements.txt
|
| 27 |
+
Lista över Python-paket som behövs:
|
| 28 |
+
- streamlit
|
| 29 |
+
- gspread
|
| 30 |
+
- google-auth
|
| 31 |
+
- pandas
|
| 32 |
+
|
| 33 |
+
### .gitignore
|
| 34 |
+
Skyddar känsliga filer från att hamna i Git:
|
| 35 |
+
- Secrets och credentials
|
| 36 |
+
- Virtuella miljöer
|
| 37 |
+
- Cache-filer
|
| 38 |
+
|
| 39 |
+
### README.md
|
| 40 |
+
Komplett dokumentation:
|
| 41 |
+
- Installation
|
| 42 |
+
- Google Cloud setup
|
| 43 |
+
- Deployment-instruktioner
|
| 44 |
+
- Felsökning
|
| 45 |
+
|
| 46 |
+
### HUGGINGFACE_SETUP.md
|
| 47 |
+
Specifik guide för deployment på Hugging Face Spaces
|
| 48 |
+
|
| 49 |
+
### secrets.toml.template
|
| 50 |
+
Mall för att skapa din egen secrets-fil
|
| 51 |
+
|
| 52 |
+
## 🚀 Snabbstart
|
| 53 |
+
|
| 54 |
+
1. **Lokalt:**
|
| 55 |
+
```bash
|
| 56 |
+
pip install -r requirements.txt
|
| 57 |
+
# Skapa .streamlit/secrets.toml enligt mall
|
| 58 |
+
streamlit run app.py
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
2. **Hugging Face Spaces:**
|
| 62 |
+
- Ladda upp app.py och requirements.txt
|
| 63 |
+
- Konfigurera secrets enligt HUGGINGFACE_SETUP.md
|
| 64 |
+
|
| 65 |
+
## 🔑 Secrets-konfiguration
|
| 66 |
+
|
| 67 |
+
### Lokal utveckling (.streamlit/secrets.toml)
|
| 68 |
+
```toml
|
| 69 |
+
sheet_id = "DIN_GOOGLE_SHEET_ID"
|
| 70 |
+
|
| 71 |
+
[gcp_service_account]
|
| 72 |
+
# Din Google Service Account JSON-data här
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### Hugging Face Spaces (Environment Variables)
|
| 76 |
+
```
|
| 77 |
+
SHEET_ID = "DIN_GOOGLE_SHEET_ID"
|
| 78 |
+
GOOGLE_CREDENTIALS = {hela JSON-objektet}
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## 💡 Nästa steg
|
| 82 |
+
|
| 83 |
+
1. Läs README.md för fullständig setup-guide
|
| 84 |
+
2. Sätt upp Google Cloud och Service Account
|
| 85 |
+
3. Konfigurera secrets
|
| 86 |
+
4. Testa lokalt först
|
| 87 |
+
5. Deploya till Hugging Face Spaces
|
| 88 |
+
|
| 89 |
+
## 🔒 Säkerhet
|
| 90 |
+
|
| 91 |
+
**VIKTIGT:** Lägg ALDRIG till dessa filer i Git:
|
| 92 |
+
- `.streamlit/secrets.toml`
|
| 93 |
+
- `*.json` (Service Account-nycklar)
|
| 94 |
+
- Virtuella miljöer (`venv/`, `env/`)
|
| 95 |
+
|
| 96 |
+
Använd alltid `.gitignore`!
|
README.md
CHANGED
|
@@ -1,13 +1,251 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
--
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏢 Områdesdata Manager
|
| 2 |
+
|
| 3 |
+
En säker Streamlit-applikation för att hantera områdesdata från Google Sheets med lösenordsskydd.
|
| 4 |
+
|
| 5 |
+
## ✨ Funktioner
|
| 6 |
+
|
| 7 |
+
- 🔐 **Lösenordsskydd per område** - Varje område har sitt eget unika lösenord
|
| 8 |
+
- 🏢 **Företagsvy** - Se alla områden för ditt företag efter inloggning
|
| 9 |
+
- ✏️ **Selektiv redigering** - Vissa fält är skrivskyddade (org.nr, företagsnamn, etc.)
|
| 10 |
+
- 💾 **Direkt synkronisering** - Ändringar sparas direkt till Google Sheets
|
| 11 |
+
- 🔄 **Realtidsuppdatering** - Hämta senaste data från arket
|
| 12 |
+
- 📱 **Responsiv design** - Fungerar på desktop och mobil
|
| 13 |
+
|
| 14 |
+
## 📋 Förutsättningar
|
| 15 |
+
|
| 16 |
+
- Python 3.8 eller senare
|
| 17 |
+
- Ett Google Cloud-konto
|
| 18 |
+
- Google Sheets med data
|
| 19 |
+
|
| 20 |
+
## 🚀 Installation och Setup
|
| 21 |
+
|
| 22 |
+
### Steg 1: Klona eller ladda ner projektet
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
# Skapa en ny mapp för projektet
|
| 26 |
+
mkdir omradesdata-manager
|
| 27 |
+
cd omradesdata-manager
|
| 28 |
+
|
| 29 |
+
# Lägg till filerna (app.py, requirements.txt)
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### Steg 2: Skapa virtuell miljö (rekommenderas)
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
# Skapa virtuell miljö
|
| 36 |
+
python -m venv venv
|
| 37 |
+
|
| 38 |
+
# Aktivera virtuell miljö
|
| 39 |
+
# På Windows:
|
| 40 |
+
venv\Scripts\activate
|
| 41 |
+
# På Mac/Linux:
|
| 42 |
+
source venv/bin/activate
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
### Steg 3: Installera dependencies
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
pip install -r requirements.txt
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### Steg 4: Google Cloud Setup
|
| 52 |
+
|
| 53 |
+
#### 4.1 Skapa Google Cloud Project
|
| 54 |
+
|
| 55 |
+
1. Gå till [Google Cloud Console](https://console.cloud.google.com/)
|
| 56 |
+
2. Klicka på "Select a project" → "New Project"
|
| 57 |
+
3. Namnge projektet (t.ex. "omradesdata-manager")
|
| 58 |
+
4. Klicka "Create"
|
| 59 |
+
|
| 60 |
+
#### 4.2 Aktivera API:er
|
| 61 |
+
|
| 62 |
+
1. I menyn, gå till "APIs & Services" → "Library"
|
| 63 |
+
2. Sök och aktivera:
|
| 64 |
+
- **Google Sheets API**
|
| 65 |
+
- **Google Drive API**
|
| 66 |
+
|
| 67 |
+
#### 4.3 Skapa Service Account
|
| 68 |
+
|
| 69 |
+
1. Gå till "APIs & Services" → "Credentials"
|
| 70 |
+
2. Klicka "Create Credentials" → "Service Account"
|
| 71 |
+
3. Namnge service account (t.ex. "sheets-manager")
|
| 72 |
+
4. Klicka "Create and Continue"
|
| 73 |
+
5. Välj roll: **Editor** (eller "Basic" → "Editor")
|
| 74 |
+
6. Klicka "Done"
|
| 75 |
+
|
| 76 |
+
#### 4.4 Generera JSON-nyckel
|
| 77 |
+
|
| 78 |
+
1. I listan över service accounts, klicka på den du nyss skapade
|
| 79 |
+
2. Gå till fliken "Keys"
|
| 80 |
+
3. Klicka "Add Key" → "Create new key"
|
| 81 |
+
4. Välj **JSON**
|
| 82 |
+
5. Klicka "Create"
|
| 83 |
+
6. En JSON-fil laddas ner automatiskt - **spara den säkert!**
|
| 84 |
+
|
| 85 |
+
#### 4.5 Dela Google Sheet med Service Account
|
| 86 |
+
|
| 87 |
+
1. Öppna din JSON-nyckel och hitta värdet för `client_email`
|
| 88 |
+
- Det ser ut som: `sheets-manager@ditt-project.iam.gserviceaccount.com`
|
| 89 |
+
2. Öppna ditt Google Sheet
|
| 90 |
+
3. Klicka "Share" (Dela)
|
| 91 |
+
4. Lägg till service account-emailen
|
| 92 |
+
5. Ge den **Editor**-behörighet
|
| 93 |
+
6. Klicka "Send"
|
| 94 |
+
|
| 95 |
+
### Steg 5: Konfigurera Secrets
|
| 96 |
+
|
| 97 |
+
#### 5.1 Hitta ditt Sheet ID
|
| 98 |
+
|
| 99 |
+
Öppna ditt Google Sheet och titta på URL:en:
|
| 100 |
+
```
|
| 101 |
+
https://docs.google.com/spreadsheets/d/DETTA_ÄR_DITT_SHEET_ID/edit
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
#### 5.2 Skapa secrets-fil
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
# Skapa .streamlit-mapp
|
| 108 |
+
mkdir .streamlit
|
| 109 |
+
|
| 110 |
+
# Kopiera mall
|
| 111 |
+
cp secrets.toml.template .streamlit/secrets.toml
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
#### 5.3 Fyll i secrets.toml
|
| 115 |
+
|
| 116 |
+
Öppna `.streamlit/secrets.toml` och:
|
| 117 |
+
|
| 118 |
+
1. Ersätt `DIN_GOOGLE_SHEET_ID_HÄR` med ditt Sheet ID
|
| 119 |
+
2. Öppna din JSON-nyckel och kopiera alla värden till motsvarande fält
|
| 120 |
+
|
| 121 |
+
Exempel:
|
| 122 |
+
```toml
|
| 123 |
+
sheet_id = "1ABC123xyz789..."
|
| 124 |
+
|
| 125 |
+
[gcp_service_account]
|
| 126 |
+
type = "service_account"
|
| 127 |
+
project_id = "omradesdata-manager-123"
|
| 128 |
+
private_key_id = "abc123..."
|
| 129 |
+
private_key = "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"
|
| 130 |
+
client_email = "sheets-manager@omradesdata-manager-123.iam.gserviceaccount.com"
|
| 131 |
+
# ... etc
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
**VIKTIGT:** Lägg till `.streamlit/secrets.toml` i `.gitignore`!
|
| 135 |
+
|
| 136 |
+
### Steg 6: Kör applikationen lokalt
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
streamlit run app.py
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
Appen öppnas automatiskt i din webbläsare på `http://localhost:8501`
|
| 143 |
+
|
| 144 |
+
## 🌐 Deployment
|
| 145 |
+
|
| 146 |
+
### Alternativ 1: Streamlit Community Cloud (Gratis)
|
| 147 |
+
|
| 148 |
+
1. Pusha din kod till GitHub (utan secrets!)
|
| 149 |
+
2. Gå till [share.streamlit.io](https://share.streamlit.io)
|
| 150 |
+
3. Klicka "New app"
|
| 151 |
+
4. Välj ditt repository
|
| 152 |
+
5. I "Advanced settings" → "Secrets", klistra in innehållet från din `secrets.toml`
|
| 153 |
+
6. Klicka "Deploy"
|
| 154 |
+
|
| 155 |
+
### Alternativ 2: Hugging Face Spaces
|
| 156 |
+
|
| 157 |
+
1. Skapa ett nytt Space på [Hugging Face](https://huggingface.co/spaces)
|
| 158 |
+
2. Välj **Streamlit** som SDK
|
| 159 |
+
3. Ladda upp `app.py` och `requirements.txt`
|
| 160 |
+
4. I Space Settings → Repository secrets:
|
| 161 |
+
- Lägg till variabel `STREAMLIT_SECRETS`
|
| 162 |
+
- Klistra in hela innehållet från din `secrets.toml`
|
| 163 |
+
5. Space kommer automatiskt att deploya
|
| 164 |
+
|
| 165 |
+
## 🔧 Anpassning
|
| 166 |
+
|
| 167 |
+
### Ändra skrivskyddade kolumner
|
| 168 |
+
|
| 169 |
+
I `app.py`, hitta variabeln `READONLY_COLUMNS` och ändra efter behov:
|
| 170 |
+
|
| 171 |
+
```python
|
| 172 |
+
READONLY_COLUMNS = [
|
| 173 |
+
'Tidstämpel',
|
| 174 |
+
'Organisationsnummer',
|
| 175 |
+
'Lösenord',
|
| 176 |
+
'Account',
|
| 177 |
+
# Lägg till fler kolumner här...
|
| 178 |
+
]
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
### Ändra layout
|
| 182 |
+
|
| 183 |
+
Koden använder Streamlit's `st.columns()` för layout. Justera kolumnbredder:
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
# Exempel: Ändra från [1, 2, 1] till [1, 3, 1]
|
| 187 |
+
col1, col2, col3 = st.columns([1, 3, 1])
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
## 📊 Datastruktur
|
| 191 |
+
|
| 192 |
+
Appen förväntar sig följande kolumner i Google Sheet:
|
| 193 |
+
|
| 194 |
+
| Kolumn | Beskrivning | Editerbar |
|
| 195 |
+
|--------|-------------|-----------|
|
| 196 |
+
| Tidstämpel | När området skapades | Nej |
|
| 197 |
+
| Organisationsnummer | Företagets org.nr | Nej |
|
| 198 |
+
| Lösenord | Unikt lösenord per område | Nej |
|
| 199 |
+
| Områdeskod | Identifierare för området | Nej |
|
| 200 |
+
| För- och efternamn | Kontaktperson | Ja |
|
| 201 |
+
| Telefonnummer | Kontakt telefon | Ja |
|
| 202 |
+
| Adress | Leveransadress | Ja |
|
| 203 |
+
| ... | Övriga fält | Ja |
|
| 204 |
+
|
| 205 |
+
## 🔒 Säkerhet
|
| 206 |
+
|
| 207 |
+
- ✅ Lösenord visas aldrig i klartext
|
| 208 |
+
- ✅ Secrets lagras säkert (inte i kod)
|
| 209 |
+
- ✅ Ingen data cachas på klientsidan
|
| 210 |
+
- ✅ Direktkommunikation med Google Sheets API
|
| 211 |
+
|
| 212 |
+
### Rekommenderade förbättringar:
|
| 213 |
+
|
| 214 |
+
1. **Rate limiting** - Begränsa antal inloggningsförsök
|
| 215 |
+
2. **Session timeout** - Automatisk utloggning efter inaktivitet
|
| 216 |
+
3. **Audit log** - Logga alla ändringar
|
| 217 |
+
4. **HTTPS** - Använd alltid HTTPS i produktion
|
| 218 |
+
|
| 219 |
+
## ❓ Felsökning
|
| 220 |
+
|
| 221 |
+
### Problem: "Kunde inte ansluta till Google Sheets"
|
| 222 |
+
|
| 223 |
+
**Lösning:**
|
| 224 |
+
1. Kontrollera att Sheet ID är korrekt
|
| 225 |
+
2. Verifiera att service account-emailen har tillgång till sheetet
|
| 226 |
+
3. Dubbelkolla att API:erna är aktiverade
|
| 227 |
+
|
| 228 |
+
### Problem: "Ogiltigt lösenord"
|
| 229 |
+
|
| 230 |
+
**Lösning:**
|
| 231 |
+
1. Kontrollera att lösenordet i sheetet matchar exakt (inga extra mellanslag)
|
| 232 |
+
2. Lösenord är case-sensitive
|
| 233 |
+
3. Uppdatera data med 🔄-knappen
|
| 234 |
+
|
| 235 |
+
### Problem: "Kan inte uppdatera celler"
|
| 236 |
+
|
| 237 |
+
**Lösning:**
|
| 238 |
+
1. Service account behöver **Editor**-behörighet, inte bara Viewer
|
| 239 |
+
2. Kontrollera att sheetet inte är skrivskyddat
|
| 240 |
+
|
| 241 |
+
## 📝 Licens
|
| 242 |
+
|
| 243 |
+
Detta projekt är öppen källkod och fritt att använda och modifiera.
|
| 244 |
+
|
| 245 |
+
## 🤝 Bidrag
|
| 246 |
+
|
| 247 |
+
Förslag och förbättringar är välkomna!
|
| 248 |
+
|
| 249 |
+
## 📧 Support
|
| 250 |
+
|
| 251 |
+
För frågor eller problem, kontakta utvecklaren.
|
START_HÄR.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚀 START HÄR!
|
| 2 |
+
|
| 3 |
+
Tack för att du använder Områdesdata Manager! Här är en snabb guide för att komma igång.
|
| 4 |
+
|
| 5 |
+
## 📦 Vad har du fått?
|
| 6 |
+
|
| 7 |
+
Du har nu alla filer du behöver för att skapa en säker webbapp för att hantera ditt Google Sheet:
|
| 8 |
+
|
| 9 |
+
✅ **app.py** - Huvudapplikationen
|
| 10 |
+
✅ **requirements.txt** - Dependencies
|
| 11 |
+
✅ **README.md** - Fullständig dokumentation
|
| 12 |
+
✅ **HUGGINGFACE_SETUP.md** - Guide för Hugging Face
|
| 13 |
+
✅ **.gitignore** - Skyddar dina secrets
|
| 14 |
+
✅ **secrets.toml.template** - Mall för konfiguration
|
| 15 |
+
✅ **PROJEKTSTRUKTUR.md** - Översikt över projektet
|
| 16 |
+
|
| 17 |
+
## 🎯 Vad gör appen?
|
| 18 |
+
|
| 19 |
+
1. **Lösenordsskydd** - Varje område har sitt eget lösenord (kolumn C i ditt sheet)
|
| 20 |
+
2. **Visa alla områden** - När användaren loggar in ser de alla områden för sitt företag
|
| 21 |
+
3. **Editera data** - Vissa kolumner kan editeras, andra är skrivskyddade
|
| 22 |
+
4. **Spara direkt** - Ändringar sparas direkt till Google Sheets
|
| 23 |
+
5. **Säker** - Data visas bara efter korrekt lösenord
|
| 24 |
+
|
| 25 |
+
## 🏃 Snabbstart (3 enkla val)
|
| 26 |
+
|
| 27 |
+
### Alternativ 1: Testa lokalt (Rekommenderas först!)
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# 1. Installera dependencies
|
| 31 |
+
pip install -r requirements.txt
|
| 32 |
+
|
| 33 |
+
# 2. Skapa .streamlit-mapp
|
| 34 |
+
mkdir .streamlit
|
| 35 |
+
|
| 36 |
+
# 3. Kopiera och fyll i secrets
|
| 37 |
+
cp secrets.toml.template .streamlit/secrets.toml
|
| 38 |
+
# Redigera .streamlit/secrets.toml med dina credentials
|
| 39 |
+
|
| 40 |
+
# 4. Kör appen
|
| 41 |
+
streamlit run app.py
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
**För lokal körning behöver du:**
|
| 45 |
+
- Google Cloud Service Account (se README.md för guide)
|
| 46 |
+
- Dela ditt Google Sheet med service account-emailen
|
| 47 |
+
- Fyll i `.streamlit/secrets.toml` med dina credentials
|
| 48 |
+
|
| 49 |
+
### Alternativ 2: Hugging Face Spaces (Gratis hosting!)
|
| 50 |
+
|
| 51 |
+
1. Gå till [huggingface.co/spaces](https://huggingface.co/spaces)
|
| 52 |
+
2. Skapa nytt Space med Streamlit SDK
|
| 53 |
+
3. Ladda upp `app.py` och `requirements.txt`
|
| 54 |
+
4. Lägg till secrets (se HUGGINGFACE_SETUP.md)
|
| 55 |
+
|
| 56 |
+
**Secrets att lägga till på Hugging Face:**
|
| 57 |
+
- `SHEET_ID` - Ditt Google Sheet ID
|
| 58 |
+
- `GOOGLE_CREDENTIALS` - Hela JSON-nyckeln från Google Cloud
|
| 59 |
+
|
| 60 |
+
### Alternativ 3: Streamlit Community Cloud
|
| 61 |
+
|
| 62 |
+
1. Pusha kod till GitHub (utan secrets!)
|
| 63 |
+
2. Gå till [share.streamlit.io](https://share.streamlit.io)
|
| 64 |
+
3. Välj ditt repo och deploya
|
| 65 |
+
4. Lägg till secrets i Advanced Settings
|
| 66 |
+
|
| 67 |
+
## 📚 Detaljerade guider
|
| 68 |
+
|
| 69 |
+
- **Fullständig setup-guide**: Läs `README.md`
|
| 70 |
+
- **Hugging Face deployment**: Läs `HUGGINGFACE_SETUP.md`
|
| 71 |
+
- **Projektöversikt**: Läs `PROJEKTSTRUKTUR.md`
|
| 72 |
+
|
| 73 |
+
## 🔧 Google Cloud Setup (Måste göras först!)
|
| 74 |
+
|
| 75 |
+
Innan du kan köra appen behöver du:
|
| 76 |
+
|
| 77 |
+
1. **Skapa Google Cloud Project**
|
| 78 |
+
- Gå till [console.cloud.google.com](https://console.cloud.google.com)
|
| 79 |
+
- Skapa nytt projekt
|
| 80 |
+
|
| 81 |
+
2. **Aktivera API:er**
|
| 82 |
+
- Google Sheets API
|
| 83 |
+
- Google Drive API
|
| 84 |
+
|
| 85 |
+
3. **Skapa Service Account**
|
| 86 |
+
- Gå till "APIs & Services" → "Credentials"
|
| 87 |
+
- Skapa Service Account
|
| 88 |
+
- Ladda ner JSON-nyckel
|
| 89 |
+
|
| 90 |
+
4. **Dela Google Sheet**
|
| 91 |
+
- Kopiera service account-emailen från JSON-filen
|
| 92 |
+
- Dela ditt Google Sheet med denna email (ge Editor-behörighet)
|
| 93 |
+
|
| 94 |
+
**Fullständig guide finns i README.md!**
|
| 95 |
+
|
| 96 |
+
## 🎨 Anpassa appen
|
| 97 |
+
|
| 98 |
+
### Ändra vilka kolumner som får editeras
|
| 99 |
+
|
| 100 |
+
I `app.py`, hitta `READONLY_COLUMNS` och ändra:
|
| 101 |
+
|
| 102 |
+
```python
|
| 103 |
+
READONLY_COLUMNS = [
|
| 104 |
+
'Tidstämpel',
|
| 105 |
+
'Organisationsnummer',
|
| 106 |
+
'Lösenord',
|
| 107 |
+
'Account',
|
| 108 |
+
# Lägg till fler här...
|
| 109 |
+
]
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
### Ändra styling
|
| 113 |
+
|
| 114 |
+
Koden använder Streamlit och kan anpassas efter dina behov. Se dokumentationen i app.py.
|
| 115 |
+
|
| 116 |
+
## ❓ Behöver du hjälp?
|
| 117 |
+
|
| 118 |
+
### Vanliga problem:
|
| 119 |
+
|
| 120 |
+
**"Kunde inte ansluta till Google Sheets"**
|
| 121 |
+
→ Kontrollera att Sheet ID är korrekt och att API:erna är aktiverade
|
| 122 |
+
|
| 123 |
+
**"Ogiltigt lösenord"**
|
| 124 |
+
→ Lösenord är case-sensitive och får inte ha extra mellanslag
|
| 125 |
+
|
| 126 |
+
**"Kan inte uppdatera celler"**
|
| 127 |
+
→ Service account behöver Editor-behörighet (inte bara Viewer)
|
| 128 |
+
|
| 129 |
+
### Mer hjälp:
|
| 130 |
+
|
| 131 |
+
- Läs felsökningsavsnittet i README.md
|
| 132 |
+
- Kolla logs om du kör på Hugging Face
|
| 133 |
+
- Testa lokalt först för enklare debugging
|
| 134 |
+
|
| 135 |
+
## 🔒 Säkerhetsregler
|
| 136 |
+
|
| 137 |
+
**VIKTIGT! Lägg ALDRIG till dessa filer i Git:**
|
| 138 |
+
- `.streamlit/secrets.toml`
|
| 139 |
+
- `*.json` (Service Account-nycklar)
|
| 140 |
+
- Din Google Sheet ID (om den är känslig)
|
| 141 |
+
|
| 142 |
+
`.gitignore` är redan konfigurerad för att skydda dig!
|
| 143 |
+
|
| 144 |
+
## 🎉 Lycka till!
|
| 145 |
+
|
| 146 |
+
Du har nu allt du behöver för att skapa en säker webbapp för att hantera ditt Google Sheet!
|
| 147 |
+
|
| 148 |
+
**Rekommenderad ordning:**
|
| 149 |
+
1. Läs README.md för fullständig förståelse
|
| 150 |
+
2. Sätt upp Google Cloud (se guide i README.md)
|
| 151 |
+
3. Testa lokalt först
|
| 152 |
+
4. Deploya till Hugging Face eller Streamlit Cloud
|
| 153 |
+
|
| 154 |
+
**Frågor?** All dokumentation finns i filerna! ✨
|
app.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import gspread
|
| 3 |
+
from google.oauth2.service_account import Credentials
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# Konfigurera sidan
|
| 10 |
+
st.set_page_config(
|
| 11 |
+
page_title="Områdesdata Manager",
|
| 12 |
+
layout="wide",
|
| 13 |
+
initial_sidebar_state="collapsed"
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Google Sheets setup
|
| 17 |
+
SCOPES = [
|
| 18 |
+
'https://www.googleapis.com/auth/spreadsheets',
|
| 19 |
+
'https://www.googleapis.com/auth/drive'
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
# Definiera vilka kolumner som INTE får editeras (0-indexerade)
|
| 23 |
+
READONLY_COLUMNS = [
|
| 24 |
+
'Tidstämpel',
|
| 25 |
+
'Organisationsnummer',
|
| 26 |
+
'Lösenord',
|
| 27 |
+
'Account', # Företagsnamn
|
| 28 |
+
'Location Name',
|
| 29 |
+
'Location - Address',
|
| 30 |
+
'Long',
|
| 31 |
+
'Lat',
|
| 32 |
+
'Location - City'
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
@st.cache_resource
|
| 36 |
+
def get_google_sheet():
|
| 37 |
+
"""Anslut till Google Sheets - fungerar både lokalt och på Hugging Face"""
|
| 38 |
+
try:
|
| 39 |
+
# Försök först med Streamlit secrets (lokal utveckling)
|
| 40 |
+
if hasattr(st, 'secrets') and 'gcp_service_account' in st.secrets:
|
| 41 |
+
creds = Credentials.from_service_account_info(
|
| 42 |
+
st.secrets["gcp_service_account"],
|
| 43 |
+
scopes=SCOPES
|
| 44 |
+
)
|
| 45 |
+
sheet_id = st.secrets["sheet_id"]
|
| 46 |
+
# Annars använd environment variables (Hugging Face Spaces)
|
| 47 |
+
elif 'GOOGLE_CREDENTIALS' in os.environ and 'SHEET_ID' in os.environ:
|
| 48 |
+
google_creds = json.loads(os.environ['GOOGLE_CREDENTIALS'])
|
| 49 |
+
creds = Credentials.from_service_account_info(
|
| 50 |
+
google_creds,
|
| 51 |
+
scopes=SCOPES
|
| 52 |
+
)
|
| 53 |
+
sheet_id = os.environ['SHEET_ID']
|
| 54 |
+
else:
|
| 55 |
+
st.error("❌ Inga credentials hittades! Kontrollera att secrets är konfigurerade.")
|
| 56 |
+
st.info("💡 Lokal utveckling: Skapa .streamlit/secrets.toml\n\n💡 Hugging Face: Lägg till GOOGLE_CREDENTIALS och SHEET_ID i Repository secrets")
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
client = gspread.authorize(creds)
|
| 60 |
+
sheet = client.open_by_key(sheet_id).sheet1
|
| 61 |
+
return sheet
|
| 62 |
+
except Exception as e:
|
| 63 |
+
st.error(f"❌ Kunde inte ansluta till Google Sheets: {e}")
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
def get_sheet_data(sheet):
|
| 67 |
+
"""Hämta all data från sheet"""
|
| 68 |
+
try:
|
| 69 |
+
data = sheet.get_all_records()
|
| 70 |
+
df = pd.DataFrame(data)
|
| 71 |
+
return df
|
| 72 |
+
except Exception as e:
|
| 73 |
+
st.error(f"❌ Fel vid hämtning av data: {e}")
|
| 74 |
+
return None
|
| 75 |
+
|
| 76 |
+
def validate_password(df, password):
|
| 77 |
+
"""Validera lösenord och returnera organisationsnummer"""
|
| 78 |
+
# Hitta rad där lösenord matchar
|
| 79 |
+
match = df[df['Lösenord'].astype(str) == password]
|
| 80 |
+
|
| 81 |
+
if not match.empty:
|
| 82 |
+
org_nr = match.iloc[0]['Organisationsnummer']
|
| 83 |
+
return org_nr
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
def get_company_areas(df, org_nr):
|
| 87 |
+
"""Hämta alla områden för ett organisationsnummer"""
|
| 88 |
+
return df[df['Organisationsnummer'] == org_nr]
|
| 89 |
+
|
| 90 |
+
def update_cell(sheet, row_num, col_name, new_value, df):
|
| 91 |
+
"""Uppdatera en specifik cell i Google Sheets"""
|
| 92 |
+
try:
|
| 93 |
+
# Hitta kolumnindex (1-baserad för gspread)
|
| 94 |
+
col_idx = df.columns.get_loc(col_name) + 1
|
| 95 |
+
# row_num är 0-baserad, lägg till 2 för header och 1-basering
|
| 96 |
+
cell_row = row_num + 2
|
| 97 |
+
|
| 98 |
+
sheet.update_cell(cell_row, col_idx, new_value)
|
| 99 |
+
return True
|
| 100 |
+
except Exception as e:
|
| 101 |
+
st.error(f"❌ Kunde inte uppdatera {col_name}: {e}")
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
def get_editable_columns(df):
|
| 105 |
+
"""Returnera lista med editerbara kolumner"""
|
| 106 |
+
return [col for col in df.columns if col not in READONLY_COLUMNS]
|
| 107 |
+
|
| 108 |
+
# CSS för bättre styling
|
| 109 |
+
st.markdown("""
|
| 110 |
+
<style>
|
| 111 |
+
.stButton button {
|
| 112 |
+
width: 100%;
|
| 113 |
+
}
|
| 114 |
+
.area-card {
|
| 115 |
+
padding: 20px;
|
| 116 |
+
border-radius: 10px;
|
| 117 |
+
background-color: #f0f2f6;
|
| 118 |
+
margin-bottom: 20px;
|
| 119 |
+
}
|
| 120 |
+
.success-msg {
|
| 121 |
+
padding: 10px;
|
| 122 |
+
border-radius: 5px;
|
| 123 |
+
background-color: #d4edda;
|
| 124 |
+
color: #155724;
|
| 125 |
+
margin: 10px 0;
|
| 126 |
+
}
|
| 127 |
+
</style>
|
| 128 |
+
""", unsafe_allow_html=True)
|
| 129 |
+
|
| 130 |
+
# Huvudapp
|
| 131 |
+
st.title("🏢 Områdesdata Manager")
|
| 132 |
+
|
| 133 |
+
# Session state
|
| 134 |
+
if 'authenticated' not in st.session_state:
|
| 135 |
+
st.session_state.authenticated = False
|
| 136 |
+
st.session_state.org_nr = None
|
| 137 |
+
st.session_state.df = None
|
| 138 |
+
st.session_state.company_name = None
|
| 139 |
+
|
| 140 |
+
# Anslut till Google Sheets
|
| 141 |
+
sheet = get_google_sheet()
|
| 142 |
+
|
| 143 |
+
if sheet is None:
|
| 144 |
+
st.stop()
|
| 145 |
+
|
| 146 |
+
# Inloggning
|
| 147 |
+
if not st.session_state.authenticated:
|
| 148 |
+
st.markdown("### 🔐 Logga in med ditt områdeslösenord")
|
| 149 |
+
|
| 150 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 151 |
+
|
| 152 |
+
with col2:
|
| 153 |
+
password = st.text_input(
|
| 154 |
+
"Ange lösenord för ditt område:",
|
| 155 |
+
type="password",
|
| 156 |
+
key="password_input",
|
| 157 |
+
placeholder="Ange lösenord..."
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
if st.button("🔓 Logga in", type="primary", use_container_width=True):
|
| 161 |
+
if password:
|
| 162 |
+
with st.spinner("Validerar lösenord..."):
|
| 163 |
+
df = get_sheet_data(sheet)
|
| 164 |
+
|
| 165 |
+
if df is not None:
|
| 166 |
+
org_nr = validate_password(df, password)
|
| 167 |
+
|
| 168 |
+
if org_nr is not None:
|
| 169 |
+
company_areas = get_company_areas(df, org_nr)
|
| 170 |
+
company_name = company_areas.iloc[0]['Account']
|
| 171 |
+
|
| 172 |
+
st.session_state.authenticated = True
|
| 173 |
+
st.session_state.org_nr = org_nr
|
| 174 |
+
st.session_state.df = df
|
| 175 |
+
st.session_state.company_name = company_name
|
| 176 |
+
st.success("✅ Inloggning lyckades!")
|
| 177 |
+
st.reflow()
|
| 178 |
+
else:
|
| 179 |
+
st.error("❌ Ogiltigt lösenord")
|
| 180 |
+
else:
|
| 181 |
+
st.warning("⚠️ Ange ett lösenord")
|
| 182 |
+
|
| 183 |
+
st.divider()
|
| 184 |
+
st.info("💡 **Information:** Detta system låter dig hantera områdesinformation. Ange ditt områdeslösenord för att se och editera tillhörande data.")
|
| 185 |
+
|
| 186 |
+
else:
|
| 187 |
+
# Användaren är inloggad
|
| 188 |
+
org_nr = st.session_state.org_nr
|
| 189 |
+
df = st.session_state.df
|
| 190 |
+
company_name = st.session_state.company_name
|
| 191 |
+
|
| 192 |
+
# Header med företagsinfo
|
| 193 |
+
col1, col2, col3 = st.columns([2, 1, 1])
|
| 194 |
+
|
| 195 |
+
with col1:
|
| 196 |
+
st.markdown(f"## 🏢 {company_name}")
|
| 197 |
+
st.caption(f"Org.nr: {org_nr}")
|
| 198 |
+
|
| 199 |
+
with col2:
|
| 200 |
+
if st.button("🔄 Uppdatera data", use_container_width=True):
|
| 201 |
+
st.cache_resource.clear()
|
| 202 |
+
st.session_state.df = get_sheet_data(sheet)
|
| 203 |
+
st.success("✅ Data uppdaterad!")
|
| 204 |
+
st.reflow()
|
| 205 |
+
|
| 206 |
+
with col3:
|
| 207 |
+
if st.button("🚪 Logga ut", use_container_width=True):
|
| 208 |
+
st.session_state.authenticated = False
|
| 209 |
+
st.session_state.org_nr = None
|
| 210 |
+
st.session_state.df = None
|
| 211 |
+
st.session_state.company_name = None
|
| 212 |
+
st.reflow()
|
| 213 |
+
|
| 214 |
+
st.divider()
|
| 215 |
+
|
| 216 |
+
# Hämta alla områden för detta företag
|
| 217 |
+
company_areas = get_company_areas(df, org_nr)
|
| 218 |
+
|
| 219 |
+
st.markdown(f"### 📍 Dina områden ({len(company_areas)} st)")
|
| 220 |
+
|
| 221 |
+
# Editerbara kolumner
|
| 222 |
+
editable_cols = get_editable_columns(df)
|
| 223 |
+
|
| 224 |
+
# Visa varje område i en expanderbar sektion
|
| 225 |
+
for idx, (row_idx, area) in enumerate(company_areas.iterrows(), 1):
|
| 226 |
+
with st.expander(f"🗺️ Område {idx}: {area['Områdeskod']} - {area['Location Name']}", expanded=(idx == 1)):
|
| 227 |
+
|
| 228 |
+
# Visa fast information
|
| 229 |
+
st.markdown("#### 📋 Fast information")
|
| 230 |
+
info_cols = st.columns(3)
|
| 231 |
+
|
| 232 |
+
with info_cols[0]:
|
| 233 |
+
st.text_input("Områdeskod", value=area['Områdeskod'], disabled=True, key=f"area_{row_idx}_kod")
|
| 234 |
+
st.text_input("Location Name", value=area['Location Name'], disabled=True, key=f"area_{row_idx}_locname")
|
| 235 |
+
|
| 236 |
+
with info_cols[1]:
|
| 237 |
+
st.text_input("Location Address", value=area['Location - Address'], disabled=True, key=f"area_{row_idx}_addr")
|
| 238 |
+
st.text_input("Location City", value=area['Location - City'], disabled=True, key=f"area_{row_idx}_city")
|
| 239 |
+
|
| 240 |
+
with info_cols[2]:
|
| 241 |
+
st.text_input("Long", value=area['Long'], disabled=True, key=f"area_{row_idx}_long")
|
| 242 |
+
st.text_input("Lat", value=area['Lat'], disabled=True, key=f"area_{row_idx}_lat")
|
| 243 |
+
|
| 244 |
+
st.divider()
|
| 245 |
+
|
| 246 |
+
# Editerbara fält
|
| 247 |
+
st.markdown("#### ✏️ Editerbar information")
|
| 248 |
+
|
| 249 |
+
# Gruppera editerbara fält
|
| 250 |
+
edit_col1, edit_col2 = st.columns(2)
|
| 251 |
+
|
| 252 |
+
changes = {} # Spara ändringar
|
| 253 |
+
|
| 254 |
+
with edit_col1:
|
| 255 |
+
# Första halvan av editerbara kolumner
|
| 256 |
+
mid = len(editable_cols) // 2
|
| 257 |
+
for col in editable_cols[:mid]:
|
| 258 |
+
current_value = str(area[col]) if pd.notna(area[col]) else ""
|
| 259 |
+
|
| 260 |
+
new_value = st.text_input(
|
| 261 |
+
col,
|
| 262 |
+
value=current_value,
|
| 263 |
+
key=f"edit_{row_idx}_{col}"
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
if new_value != current_value:
|
| 267 |
+
changes[col] = new_value
|
| 268 |
+
|
| 269 |
+
with edit_col2:
|
| 270 |
+
# Andra halvan av editerbara kolumner
|
| 271 |
+
for col in editable_cols[mid:]:
|
| 272 |
+
current_value = str(area[col]) if pd.notna(area[col]) else ""
|
| 273 |
+
|
| 274 |
+
new_value = st.text_input(
|
| 275 |
+
col,
|
| 276 |
+
value=current_value,
|
| 277 |
+
key=f"edit_{row_idx}_{col}"
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
if new_value != current_value:
|
| 281 |
+
changes[col] = new_value
|
| 282 |
+
|
| 283 |
+
# Om det finns ändringar, visa uppdateringsknapp
|
| 284 |
+
if changes:
|
| 285 |
+
st.warning(f"⚠️ {len(changes)} fält har ändrats")
|
| 286 |
+
|
| 287 |
+
cols = st.columns([1, 1, 2])
|
| 288 |
+
with cols[0]:
|
| 289 |
+
if st.button(f"💾 Spara alla ändringar", key=f"save_all_{row_idx}", type="primary", use_container_width=True):
|
| 290 |
+
success_count = 0
|
| 291 |
+
|
| 292 |
+
with st.spinner("Sparar ändringar..."):
|
| 293 |
+
for col, new_value in changes.items():
|
| 294 |
+
if update_cell(sheet, row_idx, col, new_value, df):
|
| 295 |
+
success_count += 1
|
| 296 |
+
|
| 297 |
+
if success_count == len(changes):
|
| 298 |
+
st.success(f"✅ Alla {success_count} ändringar sparade!")
|
| 299 |
+
st.cache_resource.clear()
|
| 300 |
+
st.session_state.df = get_sheet_data(sheet)
|
| 301 |
+
st.reflow()
|
| 302 |
+
else:
|
| 303 |
+
st.error(f"⚠️ Kunde bara spara {success_count} av {len(changes)} ändringar")
|
| 304 |
+
|
| 305 |
+
with cols[1]:
|
| 306 |
+
if st.button(f"↩️ Ångra ändringar", key=f"reset_{row_idx}", use_container_width=True):
|
| 307 |
+
st.reflow()
|
| 308 |
+
|
| 309 |
+
# Footer
|
| 310 |
+
st.divider()
|
| 311 |
+
st.caption(f"🔒 Säker åtkomst • Inloggad som {company_name} • {len(company_areas)} områden")
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.28.0
|
| 2 |
+
gspread>=5.11.0
|
| 3 |
+
google-auth>=2.23.0
|
| 4 |
+
pandas>=2.0.0
|
secrets.toml.template
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .streamlit/secrets.toml
|
| 2 |
+
# LÄGG INTE TILL DENNA FIL I GIT!
|
| 3 |
+
|
| 4 |
+
sheet_id = "DIN_GOOGLE_SHEET_ID_HÄR"
|
| 5 |
+
|
| 6 |
+
[gcp_service_account]
|
| 7 |
+
type = "service_account"
|
| 8 |
+
project_id = "ditt-project-id"
|
| 9 |
+
private_key_id = "xxxxxxxxxxxx"
|
| 10 |
+
private_key = "-----BEGIN PRIVATE KEY-----\nDIN_PRIVATE_KEY_HÄR\n-----END PRIVATE KEY-----\n"
|
| 11 |
+
client_email = "din-service-account@ditt-project.iam.gserviceaccount.com"
|
| 12 |
+
client_id = "xxxxxxxxxxxxx"
|
| 13 |
+
auth_uri = "https://accounts.google.com/o/oauth2/auth"
|
| 14 |
+
token_uri = "https://oauth2.googleapis.com/token"
|
| 15 |
+
auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs"
|
| 16 |
+
client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/din-service-account%40ditt-project.iam.gserviceaccount.com"
|
| 17 |
+
universe_domain = "googleapis.com"
|